Skip to main content

Overview

Lichess’s frontend is built with TypeScript and Snabbdom, a lightweight virtual DOM library. The UI is organized as a pnpm monorepo with 35+ packages, each handling specific features. The architecture emphasizes performance, modularity, and real-time updates.

Technology Stack

Core Technologies

  • TypeScript 5.9+: Type-safe JavaScript development
  • Snabbdom 3.5.1: Minimal virtual DOM library for efficient rendering
  • esbuild: Ultra-fast JavaScript bundler
  • pnpm: Fast, disk-efficient package manager
  • Chessground: Custom chess board UI component
  • Sass: CSS preprocessing

Key Libraries

Monorepo Structure

The UI is organized as a pnpm workspace defined in /pnpm-workspace.yaml:

Package Dependencies

Packages declare dependencies using workspace protocol:

Virtual DOM with Snabbdom

Why Snabbdom?

Lichess chose Snabbdom for its:
  • Minimal size: ~200 lines of core code
  • High performance: Fast diffing algorithm
  • Flexibility: Extensible module system
  • Simplicity: Easy to understand and debug

Snabbdom Basics

Snabbdom uses a hyperscript function h() to create virtual DOM nodes:

View Functions

UI components are pure functions returning VNodes:

Rendering Cycle

  1. State change: User action or server event updates controller state
  2. Redraw trigger: Controller calls redraw() function
  3. View render: View function generates new VNode tree
  4. Diff & patch: Snabbdom diffs against previous VNode and patches DOM

UI Package Architecture

Core Pattern: MVC-style Controllers

Most UI packages follow an MVC-inspired pattern:

Controller Example

From ui/analyse/src/ctrl.ts:

Bootstrap Pattern

Each page bootstraps its UI from server-provided data:

Shared Library (ui/lib)

The lib package provides common utilities used across all UI packages:
Client-side Stockfish integration:

Chessground Integration

Chessground is Lichess’s custom-built chess board component:
Chessground features:
  • SVG-based piece rendering
  • Touch and mouse input
  • Premoves and conditional moves
  • Animation and highlighting
  • Mobile-optimized

Build System

The custom build system in ui/.build/ uses esbuild:

Package Configuration

Each package defines build configuration in package.json:

Build Properties

bundle: Entry points for esbuild
  • Matches analyse.ts, analyse.study.ts, etc.
  • Output: /public/compiled/analyse.[hash].js
sync: Copy assets to public directory
  • Useful for large files (Stockfish WASM)
  • Watch mode syncs on changes
hash: Content-hash assets for CDN caching
  • Creates symlinks in /public/hashed/
  • Manifest tracks hashes for server

Build Commands

Code Splitting

esbuild automatically splits shared code into chunks:
Browsers cache shared chunks across pages.

TypeScript Configuration

Base TypeScript config in ui/tsconfig.base.json:
Each package extends base config with package-specific settings.

State Management

Lichess doesn’t use Redux or similar state libraries. State is managed in controllers:

Local State Pattern

Communication Between Components

Components communicate via:
  1. Direct calls: Parent controller calls child methods
  2. Callbacks: Child triggers parent callback
  3. PubSub: lib/pubsub for global events

Performance Optimizations

  • Keys: Use keys for list items to enable efficient reordering
  • Memoization: Cache expensive view computations
  • Lazy rendering: Defer off-screen content
  • Code splitting: Shared chunks cached across pages
  • Tree shaking: Dead code elimination
  • Content hashing: Immutable URLs for CDN caching
  • Lazy loading: Dynamic imports for large features
Browserslist targets modern browsers:
No polyfills for older browsers - encourages upgrades for security.

Testing

Unit tests use Node.js test runner:
Test files in ui/*/tests/**/*.test.ts

See Also