Skip to main content

Overview

Lichess consists of two main build systems:
  1. Scala backend: Built with sbt (Scala Build Tool)
  2. TypeScript frontend: Built with a custom build system based on esbuild
This guide covers compilation, packaging, and deployment builds.

Building the Backend

sbt Console

The primary interface for building the Scala backend is the sbt console.

Starting sbt

./lila.sh
This wrapper script:
  • Validates Java installation
  • Creates default config files if missing
  • Launches sbt with configured JVM options
The .sbtopts file configures JVM memory:
  • -J-Xms2g: Initial heap size (2GB)
  • -J-Xmx8g: Maximum heap size (8GB)
  • -J-Xss2m: Thread stack size (2MB)

Common sbt Commands

// Compile all modules
compile

Module-Specific Builds

Compile specific modules:
// Compile only the game module
game/compile

// Run tests for user module
user/test

// Clean and compile rating module
rating/clean
rating/compile

Compilation Order

The build.sbt defines module dependencies. sbt compiles modules in order:
  1. Level 1: core, coreI18n
  2. Level 2: ui, common, tree
  3. Level 3: db, room, search
  4. Level 4: memo, rating
  5. Level 5+: Feature modules (game, user, tournament, etc.)
See the Module Structure for the complete dependency graph.

Incremental Compilation

sbt uses incremental compilation:
  • Only changed files are recompiled
  • Dependent modules are recompiled if needed
  • Compilation state is cached
// Force full recompilation
clean
compile

Production Packaging

Create a production distribution:
# In sbt console
Universal/packageBin
This creates a zip file in target/universal/ containing:
  • Compiled classes
  • Dependencies
  • Startup scripts
  • Configuration

Building the Frontend

Build Script

The ui/build script handles all frontend compilation:
ui/build [options] [packages...]

Build Options

# Watch mode: rebuild on changes
ui/build -w
ui/build --watch

Building Specific Packages

Build only specific UI packages:
# Build analyse package
ui/build analyse

# Build multiple packages
ui/build analyse puzzle round

# Build with watch mode
ui/build -w analyse puzzle

Build Process

The build script performs these steps:
1
Validate Environment
2
Checks Node.js version (requires v24.11.1+):
3
Nodejs v24.11.1 or later is required.
4
Install Dependencies
5
Runs pnpm install to ensure packages are up-to-date.
6
Process Packages
7
For each package:
8
  • Bundle: Compile TypeScript entry points with esbuild
  • Compile Sass: Generate CSS from .scss files
  • Sync: Copy static assets (images, fonts, npm packages)
  • Hash: Create content-hashed symlinks for CDN caching
  • 9
    Generate Manifest
    10
    Creates manifest.[hash].json in /public/compiled/ mapping:
    11
  • Module names to hashed JavaScript URLs
  • CSS names to hashed CSS URLs
  • Inline scripts
  • 12
    {
      "analyse": "/compiled/analyse.6f3a9d2e.js",
      "analyse.css": "/compiled/analyse.8c2f5a3b.css",
      "site.inline": "/* inline JS */"
    }
    

    Build Output

    Build artifacts are written to:
    public/
    ├── compiled/           # Bundled JavaScript and CSS
    │   ├── analyse.6f3a9d2e.js
    │   ├── analyse.8c2f5a3b.css
    │   └── manifest.a1b2c3d4.json
    ├── hashed/            # Content-hashed assets
    │   └── image.e5f6g7h8.png -> ../images/image.png
    └── npm/               # Synced npm packages
        └── stockfish.wasm
    

    Watch Mode

    In watch mode, the build system:
    1. Monitors source files for changes
    2. Rebuilds affected packages incrementally
    3. Updates the manifest
    4. Prints success/error messages
    ui/build -w
    
    Example output:
    Building analyse...
    ✓ Bundled analyse.ts → analyse.6f3a9d2e.js (1.2s)
    ✓ Compiled analyse.scss → analyse.8c2f5a3b.css (0.3s)
    ✓ Created manifest.a1b2c3d4.json
    
    Watching for changes...
    

    Full Build

    To build everything from scratch:
    1
    Clean Previous Builds
    2
    # Clean sbt artifacts
    ./lila.sh
    clean
    exit
    
    # Clean UI artifacts
    ui/build --clean
    
    3
    Build Frontend
    4
    ui/build --prod
    
    5
    Build Backend
    6
    ./lila.sh
    compile
    

    Development Build Workflow

    For active development:
    1
    Terminal 1: UI Watch
    2
    ui/build -w
    
    3
    Terminal 2: sbt
    4
    ./lila.sh
    run
    
    5
    Make Changes
    6
    Edit Scala or TypeScript files. Both build systems will automatically detect changes and recompile.
    7
    Reload Browser
    8
    For frontend changes, just refresh the browser.
    9
    For backend changes, the Play Framework will reload automatically.

    Internationalization Build

    Translations are compiled during the sbt build:
    // In build.sbt
    Compile / resourceGenerators += Def.task {
      I18n.serialize(
        sourceDir = new File("translation/source"),
        destDir = new File("translation/dest"),
        dbs = List("site", "arena", "emails", ...),
        outputDir = (Compile / resourceManaged).value
      )
    }.taskValue
    
    Translation files in translation/source/ are serialized to binary format.

    Optimization

    Backend Optimization

    sbt uses:
    • Incremental compilation: Only changed files
    • Parallel compilation: Multiple modules at once (when possible)
    • Dependency caching: Downloaded dependencies cached locally

    Frontend Optimization

    esbuild provides:
    • Fast bundling: Written in Go, highly parallelized
    • Tree shaking: Removes unused code
    • Minification: Smaller file sizes (production mode)
    • Code splitting: Shared chunks across modules
    • Source maps: For debugging (development mode)

    Production Optimizations

    ui/build --prod
    
    Production mode enables:
    • Minification
    • Dead code elimination
    • Optimized chunk splitting
    • Smaller source maps (or none)

    Build Performance

    Improving sbt Performance

    1. Allocate more memory in .sbtopts:
      -J-Xms4g
      -J-Xmx16g
      
    2. Use sbt server: Keep sbt running, don’t restart
      # Keep this terminal open
      ./lila.sh
      
    3. Build specific modules:
      game/compile  // Instead of compile
      

    Improving UI Build Performance

    1. Build specific packages:
      ui/build analyse  // Instead of ui/build
      
    2. Use watch mode: Incremental rebuilds are faster
      ui/build -w
      
    3. Upgrade Node.js: Newer versions are faster

    CI/CD Builds

    GitHub Actions builds Lichess on every commit:
    # .github/workflows/build.yml
    - name: Build UI
      run: ui/build --prod
    
    - name: Test Scala
      run: sbt test
    
    See .github/workflows/ for the complete CI configuration.

    Troubleshooting

    sbt Out of Memory

    java.lang.OutOfMemoryError: Java heap space
    
    Increase heap size in .sbtopts:
    -J-Xmx16g
    

    UI Build Fails

    # Check Node.js version
    node -v  # Should be v24.11.1+
    
    # Reinstall dependencies
    rm -rf ui/node_modules
    cd ui && pnpm install
    
    # Clean build
    ui/build --clean
    

    Compilation Errors

    // In sbt, clean and retry
    clean
    compile
    
    // Or for specific module
    game/clean
    game/compile
    

    Slow Builds

    • Check system resources (CPU, RAM)
    • Close other applications
    • Use module-specific builds
    • Keep sbt running (don’t restart)

    Next Steps

    Additional Resources

    Build docs developers (and LLMs) love