Agent skill
turborepo
High-performance monorepo build system with intelligent caching, task orchestration, and parallel execution for multi-package repositories and microservices.
Install this agent skill to your Project
npx add-skill https://github.com/bobmatnyc/claude-mpm-skills/tree/main/toolchains/typescript/build/turborepo
SKILL.md
Turborepo Skill
Summary
Turborepo is a high-performance monorepo build system with intelligent caching, task orchestration, and parallel execution. It provides fast incremental builds through content-aware hashing and remote caching, ideal for managing multiple packages, apps, and shared code.
When to Use
- Multi-package repositories with shared dependencies and utilities
- Microservices architectures requiring coordinated builds
- Component libraries shared across multiple applications
- Full-stack monorepos with frontend, backend, and shared packages
- Teams needing faster CI/CD through intelligent caching and parallelization
Quick Start
Installation and Setup
# Create new Turborepo (recommended)
npx create-turbo@latest
# Or add to existing monorepo
npm install turbo --save-dev
# Initialize Turborepo
npx turbo init
Basic Structure
my-turborepo/
├── apps/
│ ├── web/ # Next.js app
│ └── api/ # Express API
├── packages/
│ ├── ui/ # Shared React components
│ ├── config/ # Shared configs (eslint, tsconfig)
│ └── utils/ # Shared utilities
├── turbo.json # Pipeline configuration
└── package.json # Root package.json
Essential turbo.json
{
"$schema": "https://turbo.build/schema.json",
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**", ".next/**"]
},
"test": {
"dependsOn": ["build"],
"outputs": ["coverage/**"]
},
"lint": {
"outputs": []
},
"dev": {
"cache": false,
"persistent": true
}
}
}
Run Tasks
# Build all packages and apps
turbo run build
# Run tests across workspace
turbo run test
# Run tasks in parallel
turbo run lint test build
# Filter by package
turbo run build --filter=web
turbo run test --filter=@myorg/ui
# Run in specific workspace
turbo run dev --filter=web...
Core Concepts
Monorepo Architecture
Turborepo manages multiple packages in a single repository:
- apps/: Application projects (Next.js, Express, etc.)
- packages/: Shared libraries and utilities
- Workspace dependencies: Internal package references
- Single version control: Unified commits and history
Task Caching
Content-aware hashing for intelligent cache invalidation:
{
"pipeline": {
"build": {
"outputs": ["dist/**", ".next/**"],
"inputs": ["src/**", "package.json"]
}
}
}
- Cache hits: Instant task completion (0ms)
- Local cache:
.turbo/cachedirectory - Remote cache: Shared across team (Vercel, custom)
- Cache invalidation: Automatic on file changes
Pipeline Configuration
Define task dependencies and execution order:
{
"pipeline": {
"build": {
"dependsOn": ["^build"], // Dependencies first
"outputs": ["dist/**"]
},
"test": {
"dependsOn": ["build"], // After local build
"outputs": ["coverage/**"]
},
"deploy": {
"dependsOn": ["build", "test"], // Multiple dependencies
"outputs": []
}
}
}
Dependency Operators:
^build: Run dependencies' build tasks first (topological)build: Run local build task first- No prefix: Run in parallel
Project Structure
Complete Monorepo Example
turborepo-example/
├── apps/
│ ├── web/ # Next.js app
│ │ ├── src/
│ │ ├── package.json
│ │ └── tsconfig.json
│ ├── admin/ # Admin dashboard
│ │ ├── src/
│ │ └── package.json
│ └── api/ # Express API
│ ├── src/
│ └── package.json
├── packages/
│ ├── ui/ # Component library
│ │ ├── src/
│ │ │ ├── Button.tsx
│ │ │ ├── Input.tsx
│ │ │ └── index.ts
│ │ ├── package.json
│ │ └── tsconfig.json
│ ├── config/ # Shared configs
│ │ ├── eslint-config/
│ │ ├── tsconfig/
│ │ └── tailwind-config/
│ ├── utils/ # Shared utilities
│ │ ├── src/
│ │ │ ├── format.ts
│ │ │ ├── validators.ts
│ │ │ └── index.ts
│ │ └── package.json
│ └── types/ # Shared TypeScript types
│ ├── src/
│ └── package.json
├── .turbo/ # Cache directory
├── turbo.json # Pipeline config
├── package.json # Root package
├── pnpm-workspace.yaml # Workspace definition
└── tsconfig.json # Root TypeScript config
Workspace Configuration
Root package.json:
{
"name": "my-turborepo",
"private": true,
"workspaces": ["apps/*", "packages/*"],
"scripts": {
"build": "turbo run build",
"dev": "turbo run dev",
"test": "turbo run test",
"lint": "turbo run lint",
"clean": "turbo run clean && rm -rf node_modules .turbo"
},
"devDependencies": {
"turbo": "latest"
},
"engines": {
"node": ">=18",
"pnpm": ">=8"
},
"packageManager": "pnpm@8.10.0"
}
pnpm-workspace.yaml:
packages:
- "apps/*"
- "packages/*"
Package Manager Integration
pnpm (Recommended)
Fast, efficient, and workspace-native:
# Install dependencies
pnpm install
# Add dependency to specific package
pnpm add react --filter=web
pnpm add @myorg/ui --filter=admin
# Add workspace dependency
cd apps/web
pnpm add @myorg/ui@workspace:*
# Run scripts
pnpm turbo run build
pnpm-workspace.yaml:
packages:
- "apps/*"
- "packages/*"
npm Workspaces
{
"workspaces": ["apps/*", "packages/*"]
}
npm install
npm run build --workspace=web
npm run test --workspaces
Yarn Workspaces
{
"workspaces": {
"packages": ["apps/*", "packages/*"]
}
}
yarn install
yarn workspace web build
yarn workspaces run test
turbo.json Configuration
Complete Configuration
{
"$schema": "https://turbo.build/schema.json",
"globalDependencies": [".env", "tsconfig.json"],
"globalEnv": ["NODE_ENV", "API_URL"],
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**", ".next/**", "build/**"],
"env": ["NEXT_PUBLIC_API_URL"],
"outputMode": "new-only"
},
"test": {
"dependsOn": ["build"],
"outputs": ["coverage/**"],
"inputs": ["src/**", "test/**", "__tests__/**"]
},
"test:unit": {
"dependsOn": [],
"outputs": [],
"cache": false
},
"lint": {
"dependsOn": [],
"outputs": []
},
"type-check": {
"dependsOn": [],
"outputs": []
},
"dev": {
"cache": false,
"persistent": true
},
"clean": {
"cache": false
}
}
}
Pipeline Options
dependsOn: Task dependencies
{
"build": {
"dependsOn": ["^build"] // Run dependencies' build first
},
"test": {
"dependsOn": ["build"] // Run local build first
},
"deploy": {
"dependsOn": ["build", "test"] // Multiple tasks
}
}
outputs: Files to cache
{
"build": {
"outputs": [
"dist/**", // Output directory
".next/**", // Next.js build
"!.next/cache/**" // Exclude cache
]
}
}
inputs: Files that invalidate cache
{
"build": {
"inputs": [
"src/**", // Source files
"package.json", // Dependencies
"tsconfig.json" // Config files
]
}
}
env: Environment variables affecting cache
{
"build": {
"env": [
"NEXT_PUBLIC_API_URL",
"DATABASE_URL"
]
}
}
outputMode: Control console output
{
"build": {
"outputMode": "new-only" // Only show new output
// "full" | "hash-only" | "new-only" | "errors-only" | "none"
}
}
cache: Enable/disable caching
{
"dev": {
"cache": false, // Don't cache dev server
"persistent": true // Keep process running
}
}
Global Configuration
globalDependencies: Files affecting all tasks
{
"globalDependencies": [
".env",
"tsconfig.json",
"package.json"
]
}
globalEnv: Environment variables for all tasks
{
"globalEnv": [
"NODE_ENV",
"CI",
"VERCEL"
]
}
Task Execution
Running Tasks
# Run single task
turbo run build
turbo run test
turbo run lint
# Run multiple tasks
turbo run build test lint
# Run in specific order (sequential)
turbo run build && turbo run test && turbo run deploy
Filtering and Scoping
Filter by package:
# Single package
turbo run build --filter=web
turbo run test --filter=@myorg/ui
# Multiple packages
turbo run build --filter=web --filter=admin
turbo run build --filter={web,admin}
Include dependencies:
# Package and its dependencies
turbo run build --filter=web...
# Package and its dependents
turbo run test --filter=...@myorg/ui
Filter by directory:
# All apps
turbo run build --filter=./apps/*
# Specific directory
turbo run test --filter=./packages/ui
Filter by git changes:
# Changed since main
turbo run build --filter=[main]
# Changed in last commit
turbo run test --filter=[HEAD^1]
# Changed packages only
turbo run lint --filter=[origin/main]
Execution Options
Parallel execution:
# Control concurrency
turbo run build --concurrency=4
turbo run test --concurrency=50%
# Unlimited parallel (default)
turbo run lint --concurrency=100%
Force execution (bypass cache):
turbo run build --force
turbo run test --force
Continue on error:
turbo run test --continue
Output modes:
turbo run build --output-logs=new-only
turbo run test --output-logs=errors-only
turbo run lint --output-logs=full
Dry run:
turbo run build --dry
turbo run build --dry=json
Environment modes:
turbo run build --env-mode=strict # Error on missing env vars
turbo run build --env-mode=loose # Allow missing env vars
Caching
Local Cache
Automatic file-based caching in .turbo/cache:
# First run: Full execution
turbo run build
# >>> FULL TURBO
# >>> build: cache miss, executing...
# Second run: Cache hit
turbo run build
# >>> FULL TURBO
# >>> build: cache hit, replaying output...
Cache structure:
.turbo/
├── cache/
│ ├── abc123.tar.zst # Compressed outputs
│ └── def456.tar.zst
└── runs/
└── xyz789.json # Run metadata
Remote Cache (Vercel)
Share cache across team and CI:
# Link to Vercel
npx turbo login
npx turbo link
# Runs will now use remote cache
turbo run build
# >>> Remote caching enabled
turbo.json with remote cache:
{
"remoteCache": {
"enabled": true
}
}
Custom Remote Cache
Self-hosted cache server:
# Configure custom cache
turbo run build --api="https://cache.mycompany.com" --token="${TURBO_TOKEN}"
Environment variables:
export TURBO_API="https://cache.mycompany.com"
export TURBO_TOKEN="your-secret-token"
export TURBO_TEAM="team-id"
Cache Configuration
Disable caching:
{
"pipeline": {
"dev": {
"cache": false
}
}
}
Cache signatures (what affects cache):
- Input files (source code, configs)
- Environment variables (env)
- Dependencies' outputs (dependsOn)
- Global dependencies (globalDependencies)
- Task configuration (turbo.json)
Invalidate cache:
# Delete local cache
rm -rf .turbo/cache
# Force rebuild
turbo run build --force
Code Sharing
Internal Packages
Shared component library:
packages/ui/
├── src/
│ ├── Button.tsx
│ ├── Input.tsx
│ └── index.ts
├── package.json
└── tsconfig.json
packages/ui/package.json:
{
"name": "@myorg/ui",
"version": "0.0.0",
"main": "./src/index.ts",
"types": "./src/index.ts",
"exports": {
".": "./src/index.ts",
"./button": "./src/Button.tsx"
},
"scripts": {
"lint": "eslint src/",
"type-check": "tsc --noEmit"
},
"devDependencies": {
"react": "^18.0.0",
"typescript": "^5.0.0"
}
}
packages/ui/src/index.ts:
export { Button } from './Button';
export { Input } from './Input';
export type { ButtonProps, InputProps } from './types';
Using in apps:
{
"dependencies": {
"@myorg/ui": "workspace:*"
}
}
import { Button, Input } from '@myorg/ui';
Shared Configuration
ESLint config package:
packages/config/eslint-config/
├── index.js
├── package.json
└── README.md
package.json:
{
"name": "@myorg/eslint-config",
"version": "0.0.0",
"main": "index.js",
"dependencies": {
"eslint": "^8.0.0",
"eslint-config-next": "^14.0.0"
}
}
index.js:
module.exports = {
extends: ['next', 'prettier'],
rules: {
'@next/next/no-html-link-for-pages': 'off',
},
};
Using in apps:
{
"devDependencies": {
"@myorg/eslint-config": "workspace:*"
}
}
.eslintrc.js:
module.exports = {
extends: ['@myorg/eslint-config'],
};
TypeScript Configuration
Shared tsconfig:
packages/config/tsconfig/
├── base.json
├── nextjs.json
├── react-library.json
└── package.json
base.json:
{
"compilerOptions": {
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"incremental": true
}
}
nextjs.json:
{
"extends": "./base.json",
"compilerOptions": {
"lib": ["dom", "dom.iterable", "esnext"],
"jsx": "preserve",
"module": "esnext",
"noEmit": true
}
}
Using in apps:
{
"extends": "@myorg/tsconfig/nextjs.json",
"compilerOptions": {
"outDir": "dist"
},
"include": ["src"],
"exclude": ["node_modules"]
}
TypeScript Project References
Root tsconfig.json:
{
"files": [],
"references": [
{ "path": "./apps/web" },
{ "path": "./apps/admin" },
{ "path": "./packages/ui" },
{ "path": "./packages/utils" }
]
}
Package tsconfig.json:
{
"extends": "@myorg/tsconfig/react-library.json",
"compilerOptions": {
"composite": true,
"outDir": "dist",
"rootDir": "src"
},
"include": ["src"],
"references": [
{ "path": "../utils" }
]
}
Build with references:
tsc --build
turbo run type-check
Integration Patterns
Next.js in Monorepo
apps/web/package.json:
{
"name": "web",
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"next": "^14.0.0",
"react": "^18.0.0",
"@myorg/ui": "workspace:*",
"@myorg/utils": "workspace:*"
}
}
apps/web/next.config.js:
/** @type {import('next').NextConfig} */
module.exports = {
transpilePackages: ['@myorg/ui', '@myorg/utils'],
reactStrictMode: true,
};
turbo.json:
{
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": [".next/**", "!.next/cache/**"]
},
"dev": {
"cache": false,
"persistent": true
}
}
}
React Component Library
packages/ui/package.json:
{
"name": "@myorg/ui",
"version": "0.0.0",
"main": "./dist/index.js",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"exports": {
".": {
"import": "./dist/index.mjs",
"require": "./dist/index.js",
"types": "./dist/index.d.ts"
}
},
"scripts": {
"build": "tsup src/index.ts --format cjs,esm --dts",
"dev": "tsup src/index.ts --format cjs,esm --dts --watch",
"lint": "eslint src/"
},
"devDependencies": {
"tsup": "^8.0.0",
"typescript": "^5.0.0",
"react": "^18.0.0"
},
"peerDependencies": {
"react": "^18.0.0"
}
}
packages/ui/tsup.config.ts:
import { defineConfig } from 'tsup';
export default defineConfig({
entry: ['src/index.ts'],
format: ['cjs', 'esm'],
dts: true,
sourcemap: true,
clean: true,
external: ['react'],
});
API Packages
packages/api-client/package.json:
{
"name": "@myorg/api-client",
"scripts": {
"build": "tsup src/index.ts --format cjs,esm --dts",
"test": "vitest run",
"type-check": "tsc --noEmit"
},
"dependencies": {
"@myorg/types": "workspace:*"
},
"devDependencies": {
"tsup": "^8.0.0",
"vitest": "^1.0.0"
}
}
src/client.ts:
import type { User, Post } from '@myorg/types';
export class APIClient {
constructor(private baseUrl: string) {}
async getUser(id: string): Promise<User> {
const response = await fetch(`${this.baseUrl}/users/${id}`);
return response.json();
}
async getPosts(): Promise<Post[]> {
const response = await fetch(`${this.baseUrl}/posts`);
return response.json();
}
}
Docker Integration
Pruned Builds
Build only what you need:
FROM node:18-alpine AS base
RUN npm install -g turbo pnpm
FROM base AS builder
WORKDIR /app
COPY . .
# Prune workspace to only include 'web' app
RUN turbo prune --scope=web --docker
FROM base AS installer
WORKDIR /app
# Copy pruned workspace
COPY --from=builder /app/out/json/ .
COPY --from=builder /app/out/pnpm-lock.yaml ./pnpm-lock.yaml
RUN pnpm install --frozen-lockfile
COPY --from=builder /app/out/full/ .
RUN pnpm turbo run build --filter=web...
FROM base AS runner
WORKDIR /app
COPY --from=installer /app/apps/web/.next/standalone ./
COPY --from=installer /app/apps/web/.next/static ./apps/web/.next/static
COPY --from=installer /app/apps/web/public ./apps/web/public
EXPOSE 3000
CMD ["node", "apps/web/server.js"]
Multi-App Docker
# Build stage with pruning
FROM node:18-alpine AS builder
RUN npm install -g turbo pnpm
WORKDIR /app
COPY . .
# Prune for specific app (passed as build arg)
ARG APP
RUN turbo prune --scope=${APP} --docker
# Install dependencies
FROM node:18-alpine AS installer
WORKDIR /app
COPY --from=builder /app/out/json/ .
COPY --from=builder /app/out/pnpm-lock.yaml ./pnpm-lock.yaml
RUN pnpm install --frozen-lockfile
# Build
COPY --from=builder /app/out/full/ .
ARG APP
RUN pnpm turbo run build --filter=${APP}...
# Runtime
FROM node:18-alpine AS runner
ARG APP
WORKDIR /app
COPY --from=installer /app .
CMD pnpm --filter=${APP} start
Build:
docker build --build-arg APP=web -t my-web-app .
docker build --build-arg APP=admin -t my-admin-app .
Docker Compose
version: '3.8'
services:
web:
build:
context: .
dockerfile: Dockerfile
args:
APP: web
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- API_URL=http://api:3001
admin:
build:
context: .
dockerfile: Dockerfile
args:
APP: admin
ports:
- "3001:3000"
environment:
- NODE_ENV=production
api:
build:
context: .
dockerfile: apps/api/Dockerfile
ports:
- "3001:3001"
environment:
- DATABASE_URL=postgres://db:5432/mydb
db:
image: postgres:15
environment:
POSTGRES_DB: mydb
POSTGRES_PASSWORD: secret
CI/CD Integration
GitHub Actions
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v2
with:
version: 8
- uses: actions/setup-node@v4
with:
node-version: 18
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build
run: pnpm turbo run build
- name: Test
run: pnpm turbo run test
- name: Lint
run: pnpm turbo run lint
With Turborepo Remote Cache
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
env:
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v2
with:
version: 8
- uses: actions/setup-node@v4
with:
node-version: 18
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build and test
run: pnpm turbo run build test lint --cache-dir=.turbo
- name: Upload cache
uses: actions/cache@v3
with:
path: .turbo
key: ${{ runner.os }}-turbo-${{ github.sha }}
restore-keys: |
${{ runner.os }}-turbo-
Changed Files Only
name: CI
on:
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: pnpm/action-setup@v2
with:
version: 8
- uses: actions/setup-node@v4
with:
node-version: 18
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build changed packages
run: pnpm turbo run build --filter=[origin/main]
- name: Test changed packages
run: pnpm turbo run test --filter=[origin/main]
Deployment Strategies
Vercel Deployment
Automatic deployment with remote cache:
{
"buildCommand": "turbo run build",
"framework": "nextjs",
"installCommand": "pnpm install",
"outputDirectory": "apps/web/.next"
}
vercel.json:
{
"buildCommand": "turbo run build --filter=web",
"installCommand": "pnpm install --frozen-lockfile",
"framework": null,
"outputDirectory": "apps/web/.next"
}
Deploy Affected Apps
# Get changed apps since last deploy
CHANGED_APPS=$(turbo run build --filter=[origin/main] --dry=json | jq -r '.packages[]')
# Deploy each changed app
for app in $CHANGED_APPS; do
echo "Deploying $app..."
# Your deployment command
done
GitHub Actions example:
- name: Get changed apps
id: changed
run: |
CHANGED=$(pnpm turbo run build --filter=[origin/main] --dry=json | jq -r '.packages | join(",")')
echo "apps=$CHANGED" >> $GITHUB_OUTPUT
- name: Deploy web
if: contains(steps.changed.outputs.apps, 'web')
run: vercel deploy --prod apps/web
- name: Deploy admin
if: contains(steps.changed.outputs.apps, 'admin')
run: vercel deploy --prod apps/admin
Docker Deployment
name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build and push Docker image
run: |
docker build --build-arg APP=web -t myregistry/web:latest .
docker push myregistry/web:latest
- name: Deploy to Kubernetes
run: |
kubectl set image deployment/web web=myregistry/web:latest
Migration from Single Repo
Step-by-Step Migration
1. Initialize Turborepo:
# In existing repo
npm install turbo --save-dev
npx turbo init
2. Create workspace structure:
mkdir -p apps/web packages/ui
3. Move existing app:
# Move current code to apps/web
git mv src apps/web/src
git mv public apps/web/public
git mv package.json apps/web/package.json
4. Update root package.json:
{
"name": "monorepo",
"private": true,
"workspaces": ["apps/*", "packages/*"],
"scripts": {
"build": "turbo run build",
"dev": "turbo run dev",
"test": "turbo run test"
},
"devDependencies": {
"turbo": "latest"
}
}
5. Configure turbo.json:
{
"$schema": "https://turbo.build/schema.json",
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**", ".next/**"]
},
"dev": {
"cache": false,
"persistent": true
}
}
}
6. Extract shared code:
# Create shared package
mkdir -p packages/ui/src
# Move components
mv apps/web/src/components packages/ui/src/
# Update imports in apps/web
7. Install dependencies:
pnpm install
8. Test:
pnpm turbo run build
pnpm turbo run dev
Gradual Migration Strategy
- Start with single app in monorepo
- Extract shared utilities to
packages/utils - Extract UI components to
packages/ui - Add second app (admin, marketing, etc.)
- Extract common configs
- Set up remote caching
- Optimize CI/CD for monorepo
Comparison with Alternatives
Turborepo vs Nx
| Feature | Turborepo | Nx |
|---|---|---|
| Caching | Content-based | Content-based |
| Remote cache | Vercel + custom | Nx Cloud + custom |
| Complexity | Minimal config | Feature-rich |
| Learning curve | Low | Medium-High |
| Code generation | No | Yes (generators) |
| Best for | Simple monorepos | Enterprise apps |
Turborepo vs Lerna
| Feature | Turborepo | Lerna |
|---|---|---|
| Caching | Advanced | Basic |
| Speed | Very fast | Slower |
| Maintenance | Active | Maintenance mode |
| Focus | Build system | Package management |
| Best for | Modern monorepos | Legacy projects |
Turborepo vs Rush
| Feature | Turborepo | Rush |
|---|---|---|
| Package manager | Any | pnpm-focused |
| Complexity | Simple | Complex |
| Configuration | Minimal | Extensive |
| Best for | Most teams | Large enterprises |
When to choose Turborepo:
- Want simple, fast monorepo builds
- Need remote caching out of the box
- Using Vercel ecosystem
- Prefer minimal configuration
- Small to medium teams
When to consider alternatives:
- Need code generation (Nx)
- Want plugin ecosystem (Nx)
- Very large enterprise (Rush)
- Legacy Lerna project (stay with Lerna)
Performance Optimization
Cache Optimization
{
"pipeline": {
"build": {
"outputs": [
"dist/**",
".next/**",
"!**/*.map", // Exclude source maps
"!.next/cache/**" // Exclude Next.js cache
]
}
}
}
Parallel Execution
# Limit concurrency for memory-constrained environments
turbo run build --concurrency=2
# Use all cores
turbo run test --concurrency=100%
# Specific number
turbo run lint --concurrency=8
Selective Execution
# Only changed packages
turbo run build --filter=[HEAD^1]
# Package and dependencies
turbo run build --filter=web...
# Exclude packages
turbo run test --filter=!admin
Environment Variable Optimization
{
"globalEnv": ["NODE_ENV", "CI"],
"pipeline": {
"build": {
"env": [
"NEXT_PUBLIC_API_URL",
"DATABASE_URL"
]
}
}
}
Only list env vars that affect output - unnecessary env vars invalidate cache.
Dependency Optimization
{
"pipeline": {
"build": {
"dependsOn": ["^build"], // Necessary
"inputs": [
"src/**/*.{ts,tsx}", // Source only
"!**/__tests__/**" // Exclude tests
]
}
}
}
Troubleshooting
Cache Not Working
Check cache hits:
turbo run build --dry=json | jq '.tasks[] | {task: .taskId, cache: .cache}'
Common causes:
- Environment variables not declared in
env - Outputs not properly configured
- Global dependencies changing
- Non-deterministic builds (timestamps, random data)
Debug caching:
# Force execution
turbo run build --force
# Check what invalidated cache
turbo run build --output-logs=hash-only
Build Failures
Dependency issues:
# Clear and reinstall
rm -rf node_modules .turbo
pnpm install --frozen-lockfile
TypeScript errors:
# Check project references
tsc --build --force
# Type check all packages
turbo run type-check
Performance Issues
Profile builds:
turbo run build --profile
Check task graph:
turbo run build --graph
Generates graph.png showing task dependencies.
Optimize concurrency:
# Too many parallel tasks can exhaust memory
turbo run build --concurrency=4
Remote Cache Not Working
Check authentication:
npx turbo login
npx turbo link
Verify environment variables:
echo $TURBO_TOKEN
echo $TURBO_TEAM
Test connection:
turbo run build --remote-only
Best Practices
Project Organization
- apps/: Application code (Next.js, Express, etc.)
- packages/: Shared libraries and utilities
- config/: Shared configurations (ESLint, TypeScript, Tailwind)
- Keep apps independent - avoid direct imports between apps
- Use workspace protocol:
"@myorg/ui": "workspace:*"
Pipeline Configuration
- Use
^buildfor topological dependencies - Configure
outputsfor all build artifacts - List only necessary
envvariables - Use
cache: falsefor dev servers and watch modes - Set
persistent: truefor long-running processes
Caching Strategy
- Enable remote cache for teams
- Use
.turbo/in.gitignore - Don't cache non-deterministic outputs
- Explicitly list all build outputs
- Use
--forceto bypass cache when needed
Workspace Dependencies
- Use workspace protocol:
workspace:* - Keep internal packages versioned at
0.0.0 - Configure package exports properly
- Use TypeScript project references for type checking
- Transpile packages in consuming apps (Next.js)
CI/CD
- Cache
.turbo/directory - Use
--filter=[origin/main]for changed packages - Set
TURBO_TOKENandTURBO_TEAMfor remote cache - Use
--frozen-lockfilefor reproducible builds - Deploy only changed apps
TypeScript
- Use project references for better type checking
- Share tsconfig via config package
- Enable
composite: truefor referenced projects - Configure
pathsfor clean imports - Use
tsuportsupfor library builds
Testing
- Run tests in parallel:
turbo run test - Use
--filterto test changed packages - Configure test outputs for caching
- Share test utilities in packages
- Mock external dependencies
Performance
- Profile with
--profileflag - Monitor cache hit rate
- Optimize
outputsconfiguration - Use concurrency limits on CI
- Enable remote caching
Code Sharing
- Extract common utilities early
- Create focused, single-purpose packages
- Document package APIs
- Version internal packages together
- Use peer dependencies for React, etc.
Advanced Patterns
Conditional Outputs
{
"pipeline": {
"build": {
"outputs": [
"dist/**",
".next/**",
"{projectRoot}/build/**" // Project-specific
]
}
}
}
Task-Specific Environment
{
"pipeline": {
"build:production": {
"dependsOn": ["^build"],
"env": ["NODE_ENV", "API_URL"],
"outputs": ["dist/**"]
},
"build:staging": {
"dependsOn": ["^build"],
"env": ["NODE_ENV", "STAGING_API_URL"],
"outputs": ["dist/**"]
}
}
}
Workspace Filtering Examples
# Build web and its dependencies
turbo run build --filter=web...
# Build everything that depends on ui
turbo run build --filter=...ui
# Build changed packages and dependents
turbo run build --filter=[main]...
# Build specific scope
turbo run build --filter=@myorg/*
# Exclude packages
turbo run test --filter=!admin --filter=!legacy
Multiple Entry Points
{
"name": "@myorg/ui",
"exports": {
".": "./src/index.ts",
"./button": "./src/button.tsx",
"./input": "./src/input.tsx",
"./hooks": "./src/hooks/index.ts"
}
}
import { Button } from '@myorg/ui/button';
import { useDebounce } from '@myorg/ui/hooks';
Shared Build Scripts
packages/scripts/
├── build.js
├── test.js
└── package.json
build.js:
#!/usr/bin/env node
const { build } = require('tsup');
build({
entry: ['src/index.ts'],
format: ['cjs', 'esm'],
dts: true,
sourcemap: true,
clean: true,
}).catch((error) => {
console.error(error);
process.exit(1);
});
Using in packages:
{
"scripts": {
"build": "node ../../packages/scripts/build.js"
}
}
Resources
Official Documentation
Community
Related Tools
- pnpm - Fast package manager
- tsup - TypeScript bundler
- changesets - Version management
- Vercel - Deployment platform
This skill provides comprehensive Turborepo knowledge for building high-performance monorepos with intelligent caching and task orchestration.
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
bad-example-skill
ANTI-PATTERN - Example showing violations of self-containment (DO NOT COPY)
example-framework-skill
Example of a properly self-contained skill following all best practices
systematic-debugging
Systematic debugging methodology emphasizing root cause analysis over quick fixes
verification-before-completion
Run verification commands and confirm output before claiming success
Root Cause Tracing
Systematically trace bugs backward through call stack to find original trigger
Brainstorming Ideas Into Designs
Interactive idea refinement using Socratic method to develop fully-formed designs
Didn't find tool you were looking for?