Skip to content

Turborepo

Turborepo is a high-performance build system for JavaScript and TypeScript monorepos. It understands the task graph of your monorepo, caches outputs locally and remotely, and only re-runs tasks that have changed. The result is dramatically faster CI and local development.

Installation

# Add to existing monorepo
pnpm add -D -w turbo
npm install --save-dev turbo
yarn add --dev turbo

# Install globally
npm install -g turbo
pnpm add -g turbo

# Create a new Turborepo from scratch
npx create-turbo@latest
pnpm dlx create-turbo@latest

# Create with specific package manager
pnpm dlx create-turbo@latest --package-manager pnpm

# Check version
turbo --version

# Upgrade
npx @turbo/codemod upgrade

Configuration

Turborepo is configured via turbo.json at the monorepo root:

{
  "$schema": "https://turbo.build/schema.json",
  "ui": "tui",
  "tasks": {
    "build": {
      "dependsOn": ["^build"],
      "inputs": ["$TURBO_DEFAULT$", ".env.production"],
      "outputs": ["dist/**", ".next/**", "!.next/cache/**"]
    },
    "test": {
      "dependsOn": ["^build"],
      "inputs": ["$TURBO_DEFAULT$", "tests/**"],
      "outputs": [],
      "cache": true
    },
    "lint": {
      "dependsOn": [],
      "inputs": ["$TURBO_DEFAULT$", ".eslintrc.js"],
      "outputs": []
    },
    "dev": {
      "cache": false,
      "persistent": true
    },
    "typecheck": {
      "dependsOn": ["^typecheck"],
      "outputs": []
    },
    "clean": {
      "cache": false
    }
  }
}

Package-Level Task Override

// apps/web/turbo.json
{
  "extends": ["//"],
  "tasks": {
    "build": {
      "env": ["NEXT_PUBLIC_API_URL", "NEXT_PUBLIC_GA_ID"],
      "outputs": [".next/**", "!.next/cache/**"]
    }
  }
}

Environment Variables in Cache Keys

{
  "tasks": {
    "build": {
      "env": ["NODE_ENV", "API_URL"],
      "passThroughEnv": ["AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY"],
      "outputs": ["dist/**"]
    }
  },
  "globalEnv": ["CI", "TURBO_TEAM"],
  "globalPassThroughEnv": ["VERCEL_ENV"]
}

Core Commands

CommandDescription
turbo run buildRun build across all packages
turbo buildShorthand for turbo run build
turbo run build test lintRun multiple tasks
turbo run build --filter=webRun in specific package
turbo run build --filter="./apps/*"Run in matching packages
turbo run build --filter="...[HEAD^1]"Run on changed packages
turbo run build --dry-runPreview what would run
turbo run build --dry-run=jsonPreview as JSON output
turbo run build --forceIgnore cache, re-run all
turbo run build --no-cacheRun without writing to cache
turbo run build --output-logs=new-onlyShow only non-cached output
turbo run build --output-logs=noneSuppress all output
turbo run build --concurrency=4Limit parallel tasks
turbo run build --graphGenerate task dependency graph
turbo run build --graph=output.pngSave graph as image
turbo run dev --parallelRun all dev tasks in parallel
turbo loginLog in to Vercel Remote Cache
turbo logoutLog out of Remote Cache
turbo linkLink to Vercel Remote Cache
turbo unlinkUnlink from Remote Cache
turbo prune --scope=webPrune monorepo for a package
turbo generateRun a workspace generator
turbo daemon statusCheck Turborepo daemon status

Advanced Usage

Dependency Graph (dependsOn)

{
  "tasks": {
    "build": {
      "dependsOn": ["^build"]
    }
  }
}
PatternMeaning
"^build"Run build in all dependency packages first (topological)
"build"Run build in the same package first
"ui#build"Run build in the ui package specifically first
[]No dependencies — run immediately

Remote Caching

# Vercel Remote Cache (free with Vercel account)
turbo login
turbo link

# Set team slug for shared cache
TURBO_TEAM=my-team turbo run build
TURBO_TOKEN=your-token TURBO_TEAM=my-team turbo run build

# Self-hosted remote cache (open source)
# Set environment variables:
export TURBO_API="https://my-cache.example.com"
export TURBO_TEAM="my-team"
export TURBO_TOKEN="my-token"
turbo run build

Pruning for Docker

# Create a minimal monorepo subset for one app
turbo prune web --docker

# Output structure:
# out/
#   json/   — package.json files only (for dep install layer)
#   full/   — full source (for build layer)
#   yarn.lock (or pnpm-lock.yaml)
FROM node:20-alpine AS pruner
RUN npm install -g turbo
WORKDIR /app
COPY . .
RUN turbo prune web --docker

FROM node:20-alpine AS installer
WORKDIR /app
COPY --from=pruner /app/out/json/ .
COPY --from=pruner /app/out/pnpm-lock.yaml ./pnpm-lock.yaml
RUN npm install -g pnpm && pnpm install --frozen-lockfile

FROM node:20-alpine AS builder
WORKDIR /app
COPY --from=installer /app/node_modules ./node_modules
COPY --from=pruner /app/out/full/ .
RUN pnpm turbo run build --filter=web

FROM node:20-alpine AS runner
WORKDIR /app
COPY --from=builder /app/apps/web/.next ./.next
EXPOSE 3000
CMD ["node", "server.js"]

Filtering Syntax

# By package name
turbo run test --filter=my-app

# By glob (must quote)
turbo run lint --filter="./packages/*"
turbo run lint --filter="./apps/web"

# Changed since a git ref
turbo run test --filter="...[HEAD~1]"  # changed in last commit
turbo run test --filter="...[main]"    # changed vs main branch
turbo run test --filter="...[origin/main]"

# Dependents (packages that depend on X)
turbo run build --filter="...^my-lib"  # apps that use my-lib

# Dependencies of a package
turbo run build --filter="my-app^..."  # build deps of my-app

# Exclude a package
turbo run build --filter='!./packages/legacy'

Workspace Generators

# List available generators
turbo generate

# Create a new package from template
turbo generate workspace --name my-new-package

# Custom generators live in turbo/generators/
# config.ts defines the generator interface

CI with GitHub Actions

name: CI
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  build:
    name: Build and Test
    runs-on: ubuntu-latest
    env:
      TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
      TURBO_TEAM: ${{ vars.TURBO_TEAM }}
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0  # needed for --filter="...[HEAD~1]"
      - uses: pnpm/action-setup@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'pnpm'
      - run: pnpm install --frozen-lockfile
      - run: pnpm turbo run lint typecheck build test

Common Workflows

Local Development

# Run dev servers for all apps simultaneously
turbo run dev

# Run dev only for specific app and its deps
turbo run dev --filter=web...

# Build and watch a library as you develop
turbo run build --filter=ui --watch

Checking Cache Hit Rate

# See cache status with summary
turbo run build --output-logs=new-only

# Full run stats
turbo run build --summarize

# Dry run to preview (no actual execution)
turbo run build --dry-run=json | jq '.packages[].tasks[] | {task: .taskId, cached: .cache.local}'

Affected Package Builds in PRs

# Only run tasks on packages changed in this PR vs main
turbo run build test --filter="...[origin/main]"

# Include their dependents too (apps that use changed packages)
turbo run build test --filter="...^[origin/main]..."

Tips and Best Practices

Start with turbo.json outputs — Correctly specifying outputs is the most important thing for cache correctness. If you don’t list an output directory, Turborepo can’t restore it from cache.

Use $TURBO_DEFAULT$ for inputs — This special token includes all files tracked by git that aren’t in a .gitignore, plus any files you add. It’s the correct default — don’t use ["**/*"] which is too broad.

Set "cache": false for dev tasks — Development servers have side effects (ports, watchers) and should never be cached. Always set "persistent": true as well so Turbo doesn’t treat them as finished tasks.

Enable Remote Cache early — Even on solo projects, Vercel’s free remote cache shares builds between your local machine and CI. This dramatically speeds up CI for large monorepos.

Use --output-logs=new-only — In CI, suppress cached task output to keep logs readable. You’ll still see all failures.

Avoid glob-heavy inputs — Specific inputs patterns produce more cache hits. If your lint task only depends on *.ts files, say so — changes to .md files won’t bust the cache.

Prune for Docker builds — Always use turbo prune when building Docker images. It creates a minimal monorepo subset with no unnecessary packages, producing much smaller layers and faster builds.

Use the TUI — Set "ui": "tui" in turbo.json to enable the interactive terminal UI that shows task progress in real time with per-package status.