turborepo

v2026.09.24

High-performance monorepo build system with intelligent caching, task orchestration, and parallel execution for multi-package repositories and microservices.

GitHub
Install command
npx skhub add bobmatnyc/turborepo
Markdown
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/cache directory
  • 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

  1. Start with single app in monorepo
  2. Extract shared utilities to packages/utils
  3. Extract UI components to packages/ui
  4. Add second app (admin, marketing, etc.)
  5. Extract common configs
  6. Set up remote caching
  7. Optimize CI/CD for monorepo

Comparison with Alternatives

Turborepo vs Nx

FeatureTurborepoNx
CachingContent-basedContent-based
Remote cacheVercel + customNx Cloud + custom
ComplexityMinimal configFeature-rich
Learning curveLowMedium-High
Code generationNoYes (generators)
Best forSimple monoreposEnterprise apps

Turborepo vs Lerna

FeatureTurborepoLerna
CachingAdvancedBasic
SpeedVery fastSlower
MaintenanceActiveMaintenance mode
FocusBuild systemPackage management
Best forModern monoreposLegacy projects

Turborepo vs Rush

FeatureTurborepoRush
Package managerAnypnpm-focused
ComplexitySimpleComplex
ConfigurationMinimalExtensive
Best forMost teamsLarge 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 ^build for topological dependencies
  • Configure outputs for all build artifacts
  • List only necessary env variables
  • Use cache: false for dev servers and watch modes
  • Set persistent: true for 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 --force to 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_TOKEN and TURBO_TEAM for remote cache
  • Use --frozen-lockfile for reproducible builds
  • Deploy only changed apps

TypeScript

  • Use project references for better type checking
  • Share tsconfig via config package
  • Enable composite: true for referenced projects
  • Configure paths for clean imports
  • Use tsup or tsup for library builds

Testing

  • Run tests in parallel: turbo run test
  • Use --filter to test changed packages
  • Configure test outputs for caching
  • Share test utilities in packages
  • Mock external dependencies

Performance

  • Profile with --profile flag
  • Monitor cache hit rate
  • Optimize outputs configuration
  • 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


This skill provides comprehensive Turborepo knowledge for building high-performance monorepos with intelligent caching and task orchestration.

Discovery
Tags

No tags published for this skill.

Version
Latest version metadata

Version

v2026.09.24

Published

Sep 24, 2026

Category

Uncategorized

License

MIT

Source path

toolchains/typescript/build/turborepo

Default branch

main

Latest commit

718070a

Tree SHA

3b7f612