Turborepo
You are an expert in Turborepo v2.8 — the high-performance, Rust-powered build system for JavaScript/TypeScript monorepos by Vercel. Provide precise, actionable guidance for task pipelines, caching, filtering, CI optimization, and architectural boundary enforcement.
When to Use
Activate this skill when the user is working with any of the following:
- turbo.json configuration — defining task pipelines, outputs, inputs, env vars, cache rules
- Monorepo build orchestration — multiple apps or packages sharing code
- CI optimization —
--affectedflag, remote caching, dynamic matrix jobs - Workspace filtering —
--filtersyntax for scoped task execution - Boundary enforcement — architectural constraints via
boundariesin turbo.json - Microfrontend orchestration — independent deploys with shared packages
- Bun workspace support — lockfile detection, granular cache invalidation
- Upgrading Turborepo — codemod migration, version-specific features
Do NOT recommend Turborepo for a single Next.js app without shared code — standard Turopack or Next.js built-in tooling is simpler.
Prerequisites
- Node.js 18+ (Node 22 recommended for latest features)
- Package manager: npm, pnpm, yarn, or Bun (Bun support stable since 2.6; requires
bun.locktext format) - Git — required for
--affectedand--filter=[branch]to compute changed files - Windows host (PowerShell): Commands below use POSIX-style flags. In PowerShell, wrap glob patterns in single quotes or use
--filter='web...'to avoid splatting issues. For multi-line YAML in CI, use standard GitHub Actions runners (Ubuntu) — Windows runners are supported but not primary for CI.
Procedure
1. Install or Upgrade Turborepo
# Create a new monorepo
npx create-turbo@latest
# Add to existing monorepo
npm install turbo --save-dev
# Upgrade existing Turborepo to latest
npx @turbo/codemod migrate
2. Define turbo.json Task Pipeline
Create or edit turbo.json at the repository root:
{
"$schema": "https://turborepo.dev/schema.json",
"globalDependencies": [".env"],
"globalEnv": ["CI", "NODE_ENV"],
"tasks": {
"build": {
"description": "Compile TypeScript and bundle the application",
"dependsOn": ["^build"],
"outputs": [".next/**", "dist/**"],
"env": ["DATABASE_URL", "NEXT_PUBLIC_API_URL"],
"inputs": ["src/**", "package.json", "tsconfig.json"]
},
"test": {
"description": "Run the test suite",
"dependsOn": ["build"],
"outputs": ["coverage/**"],
"env": ["TEST_DATABASE_URL"]
},
"test:unit": {
"dependsOn": [],
"outputs": ["coverage/**"]
},
"lint": {
"description": "Lint source files",
"inputs": ["src/**", ".eslintrc.*"]
},
"typecheck": {
"dependsOn": ["^build"],
"inputs": ["src/**", "tsconfig.json"]
},
"db:generate": {
"cache": false
},
"dev": {
"cache": false,
"persistent": true
},
"clean": {
"cache": false
}
}
}
Key configuration fields:
| Field | Meaning |
|---|---|
dependsOn: ["^build"] |
Run build in dependencies first (^ = topological) |
dependsOn: ["build"] |
Run build in the same package first (no ^) |
outputs |
Files to cache (build artifacts) |
inputs |
Files that affect the task hash (default: all non-gitignored files) |
env |
Environment variables that affect the task hash |
cache: false |
Skip caching (dev servers, codegen) |
persistent: true |
Long-running tasks (dev servers) |
globalDependencies |
Files that invalidate all task caches when changed |
globalEnv |
Env vars that invalidate all task caches when changed |
3. Composable Configuration (2.7+)
Package-level turbo.json can extend from any workspace package:
// packages/ui/turbo.json
{
"extends": ["@myorg/config"],
"tasks": {
"build": {
"outputs": ["dist/**"]
}
}
}
4. Run Tasks with Workspace Filtering
# Single package
turbo build --filter=web
# Package and its dependencies
turbo build --filter=web...
# Package and its dependents (what depends on it)
turbo build --filter=...ui
# Multiple packages
turbo build --filter=web --filter=api
# By directory
turbo build --filter=./apps/*
# Packages that changed since main
turbo build --filter=[main]
# Combine: changed packages and their dependents
turbo build --filter=...[main]
# Exclude a package
turbo build --filter=!docs
# Packages matching a pattern
turbo build --filter=@myorg/*
Filter syntax reference:
| Pattern | Meaning |
|---|---|
web |
Only the web package |
web... |
web and all its dependencies |
...web |
web and all its dependents |
...web... |
web, its dependencies, and its dependents |
./apps/* |
All packages in the apps/ directory |
[main] |
Packages changed since main branch |
{./apps/web}[main] |
web only if it changed since main |
!docs |
Exclude the docs package |
5. Use --affected for Incremental CI
The single most important CI optimization:
# Only build/test packages that changed since main
turbo build test lint --affected
This performs intelligent graph traversal:
- Identifies changed files since the base branch
- Maps changes to affected packages
- Includes all dependent packages (transitively)
- Runs tasks only for the affected subgraph
6. Set Up Remote Caching
# Login to Vercel for remote caching
turbo login
# Link to a Vercel team
turbo link
# Builds now share cache across all machines and CI
turbo build
For CI, set environment variables (use placeholders — never commit real tokens):
TURBO_TOKEN=YOUR_TOKEN
TURBO_TEAM=YOUR_TEAM
turbo build
7. Configure CI Pipeline (GitHub Actions)
Basic parallel job with --affected:
name: CI
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # Required for --affected
- uses: actions/setup-node@v4
with:
node-version: 22
- run: npm ci
- run: turbo build test lint --affected
env:
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
Dynamic matrix from workspace list:
jobs:
detect:
runs-on: ubuntu-latest
outputs:
packages: ${{ steps.list.outputs.packages }}
steps:
- uses: actions/checkout@v4
- id: list
run: |
PACKAGES=$(turbo ls --affected --output=json | jq -c '[.[].name]')
echo "packages=$PACKAGES" >> "$GITHUB_OUTPUT"
test:
needs: detect
if: needs.detect.outputs.packages != '[]'
runs-on: ubuntu-latest
strategy:
matrix:
package: ${{ fromJson(needs.detect.outputs.packages) }}
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: turbo test --filter=${{ matrix.package }}
8. Enforce Architectural Boundaries
Add boundaries to turbo.json:
{
"boundaries": {
"tags": {
"apps/*": ["app"],
"packages/ui": ["shared", "ui"],
"packages/utils": ["shared"],
"packages/config": ["config"]
},
"rules": [
{ "from": ["app"], "allow": ["shared"] },
{ "from": ["shared"], "deny": ["app"] }
]
}
}
Check compliance:
turbo boundaries
Add to your pipeline:
{
"tasks": {
"check": {
"dependsOn": ["lint", "typecheck", "boundaries"]
},
"boundaries": {}
}
}
9. Use Watch Mode for Development
# Watch a specific task — re-executes on file changes
turbo watch test
# Watch with a filter
turbo watch test --filter=web
# Watch multiple tasks
turbo watch test lint
Watch mode respects the task graph — if test depends on build, changing a source file re-runs build first, then test.
Persistent tasks vs watch:
persistent: truein turbo.json: The task itself is long-running (e.g.,next dev). Turbo starts it and keeps it alive.turbo watch: Turbo re-invokes the task on file changes. Use for tasks that run and exit (e.g.,vitest run,tsc --noEmit).
10. Visualize and Dry-Run the Task Graph
# Print graph to terminal
turbo build --graph
# Output as DOT format (Graphviz)
turbo build --graph=graph.dot
# Output as JSON
turbo build --graph=graph.json
# Open interactive graph in browser
turbo build --graph=graph.html
# Show tasks that would run without executing
turbo build --dry-run
# JSON output for programmatic use
turbo build --dry-run=json
11. Prune for Single-App Deployment
# Generate minimal monorepo for deploying a single app
turbo prune web --docker
12. Devtools & AI Docs (2.8+)
# Visual package/task graph explorer (hot-reloads on changes)
turbo devtools
# Search Turborepo docs from the terminal (returns agent-friendly markdown)
turbo docs
turbo docsoutput is optimized for AI coding agents — markdown format preserves context windows.
13. Bun Workspace Support (2.6+)
# Ensure text lockfile for Turborepo compatibility
bun install --save-text-lockfile
# Run only affected packages (works with Bun lockfile detection)
turbo build --affected
Turborepo parses bun.lock (text format) for granular cache invalidation — only affected tasks are invalidated, not the entire monorepo. If only bun.lockb (binary) is found, Turborepo errors with a prompt to generate a text lockfile.
14. Microfrontend Orchestration
Structure for independent deploys with shared packages:
my-platform/
├── turbo.json
├── package.json
├── apps/
│ ├── shell/ # Layout / shell app (owns top-level routing)
│ ├── dashboard/ # Micro-app
│ ├── settings/ # Micro-app
│ └── marketing/ # Micro-app
└── packages/
├── ui/ # Shared component library
├── auth/ # Shared auth utilities
└── config/ # Shared tsconfig, eslint
# Deploy only the dashboard micro-app
turbo build --filter=dashboard
# Deploy all micro-apps in parallel
turbo build --filter=./apps/*
# Deploy only micro-apps that changed since main
turbo build --filter=./apps/*...[main]
Combine with boundary rules to enforce architectural isolation:
{
"boundaries": {
"tags": {
"apps/*": ["micro-app"],
"packages/ui": ["shared"],
"packages/auth": ["shared"]
},
"rules": [
{ "from": ["micro-app"], "allow": ["shared"] },
{ "from": ["shared"], "deny": ["micro-app"] }
]
}
}
When to use Turborepo for microfrontends:
| Scenario | Recommended? |
|---|---|
| Multiple teams owning independent features | Yes — independent deploys + shared packages |
| Single team, single app | No — standard Next.js is simpler |
| Shared component library across apps | Yes — packages/ui with boundary rules |
| Gradual migration from monolith | Yes — extract features into micro-apps incrementally |
| Need version-skew protection | Yes — isolated builds per micro-app |
Pitfalls
fetch-depth: 0is mandatory for--affected— Without full git history, Turborepo cannot compute changed files. Always setfetch-depth: 0inactions/checkout.- Bun binary lockfile (
bun.lockb) is not supported — Turborepo requiresbun.locktext format. Runbun install --save-text-lockfileto generate it. Without it, Turborepo errors. turbo prunewith Bun 1.3+ may produce broken lockfiles — Known issue: formatting differences can breakbun i --frozen-lockfile. Track fixes at turborepo#11007.- Forgetting
outputsmeans no caching — If you don't declareoutputs, Turborepo caches the task exit code but not artifacts. Always specify build output directories. persistent: truetasks cannot be cached — Always setcache: falsealongsidepersistent: truefor dev servers. Turbo will warn if you don't.inputstoo narrow can cause stale caches — If you specifyinputsand miss a file (e.g.,.env), changes to that file won't invalidate the cache. UseglobalDependenciesfor root-level files.- PowerShell glob splatting — In PowerShell,
--filter=web...may be interpreted as splatting. Wrap in single quotes:--filter='web...'. - Remote cache token leakage — Never hardcode
TURBO_TOKENin source files or turbo.json. Use CI secrets or.env(gitignored). UseYOUR_TOKENplaceholders in documentation. ^buildvsbuildconfusion —^buildmeans "build my dependencies first" (topological).build(no^) means "build this same package first" (sequential within package). Mixing these up causes circular dependencies or missing builds.- Composable config
extendsrequires published package — Theextendsfield references a workspace package name, not a file path. The package must exist in the workspace and be resolvable.
Verification
Verify Turborepo is installed and version is correct
npx turbo --version
# Expected: 2.8.x or higher
Verify task graph is valid
turbo build --dry-run
# Expected: lists all tasks that would execute with cache status (HIT/MISS)
Verify caching works
# First run — should be MISS
turbo build
# Second run — should be HIT
turbo build
# Expected output: "FULL TURBO" or cache HIT messages
Verify --affected detects changes correctly
# On main branch, make a change to one package
git checkout -b test-affected
echo "// test" >> packages/ui/src/index.ts
git add -A && git commit -m "test"
# Run affected — should only show ui and its dependents
turbo build --affected
# Expected: only packages that depend on ui are built
Verify boundary rules pass
turbo boundaries
# Expected: no violations, exit code 0
Verify remote cache is connected
turbo login
# Expected: browser opens for Vercel authentication
turbo link
# Expected: links to your Vercel team
turbo build
# Expected: "Remote cache HIT" or "Remote cache MISS" in output
Verify Bun lockfile compatibility
# Check lockfile format
ls bun.lock
# If only bun.lockb exists:
bun install --save-text-lockfile
ls bun.lock # Should now exist
Examples
Minimal turbo.json for a Next.js + API monorepo
{
"$schema": "https://turborepo.dev/schema.json",
"tasks": {
"build": {
"dependsOn": ["^build"],
"outputs": [".next/**", "dist/**"]
},
"dev": {
"cache": false,
"persistent": true
},
"lint": {},
"test": {
"dependsOn": ["build"]
}
}
}
Standard monorepo structure
my-monorepo/
├── turbo.json
├── package.json
├── apps/
│ ├── web/ # Next.js app
│ ├── api/ # Backend service
│ └── docs/ # Documentation site
├── packages/
│ ├── ui/ # Shared component library
│ ├── config/ # Shared configs (eslint, tsconfig)
│ └── utils/ # Shared utilities
└── node_modules/
Related skills
- nextjs — Next.js app configuration and deployment
- vercel — Vercel deployment platform integration
- github-actions — CI/CD pipeline configuration
- bun — Bun runtime and package manager