Agent skill

monorepo-turborepo

Manage monorepos with Turborepo for workspace configuration, task caching, and CI optimization. Use when building multi-package projects, shared libraries, or optimizing large codebases.

Stars 163
Forks 31

Install this agent skill to your Project

npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/data/monorepo-turborepo

SKILL.md

Turborepo Monorepo Management

Expert guidance for building and managing monorepos with Turborepo. Covers workspace setup, pipeline configuration, caching strategies, and CI/CD optimization.

Quick Start

bash
# Create new Turborepo monorepo
npx create-turbo@latest my-monorepo

# Add Turborepo to existing monorepo
npm install turbo --save-dev

# Run all build tasks
turbo run build

# Run with cache bypass
turbo run build --force

Workspace Configuration

pnpm Workspaces (Recommended)

yaml
# pnpm-workspace.yaml
packages:
  - "apps/*"
  - "packages/*"
  - "tools/*"
json
// package.json (root)
{
  "name": "my-monorepo",
  "private": true,
  "scripts": {
    "build": "turbo run build",
    "dev": "turbo run dev",
    "lint": "turbo run lint",
    "test": "turbo run test",
    "clean": "turbo run clean && rm -rf node_modules"
  },
  "devDependencies": {
    "turbo": "^2.0.0"
  },
  "packageManager": "pnpm@9.0.0"
}

npm Workspaces

json
// package.json (root)
{
  "name": "my-monorepo",
  "private": true,
  "workspaces": ["apps/*", "packages/*"],
  "scripts": {
    "build": "turbo run build",
    "dev": "turbo run dev"
  }
}

Yarn Workspaces

json
// package.json (root)
{
  "name": "my-monorepo",
  "private": true,
  "workspaces": {
    "packages": ["apps/*", "packages/*"],
    "nohoist": ["**/react-native", "**/react-native/**"]
  }
}

turbo.json Pipeline Configuration

Basic Configuration

json
// turbo.json
{
  "$schema": "https://turbo.build/schema.json",
  "globalDependencies": [".env", "tsconfig.json"],
  "globalEnv": ["NODE_ENV", "CI"],
  "tasks": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": ["dist/**", ".next/**", "build/**"],
      "env": ["API_URL", "DATABASE_URL"]
    },
    "dev": {
      "cache": false,
      "persistent": true
    },
    "lint": {
      "dependsOn": ["^build"],
      "outputs": []
    },
    "test": {
      "dependsOn": ["build"],
      "outputs": ["coverage/**"],
      "env": ["CI", "TEST_DATABASE_URL"]
    },
    "clean": {
      "cache": false
    }
  }
}

Advanced Pipeline with Inputs

json
{
  "$schema": "https://turbo.build/schema.json",
  "tasks": {
    "build": {
      "dependsOn": ["^build"],
      "inputs": [
        "src/**",
        "package.json",
        "tsconfig.json",
        "!src/**/*.test.ts"
      ],
      "outputs": ["dist/**"],
      "outputLogs": "new-only"
    },
    "typecheck": {
      "dependsOn": ["^build"],
      "inputs": ["**/*.ts", "**/*.tsx", "tsconfig.json"],
      "outputs": []
    },
    "test:unit": {
      "inputs": ["src/**", "tests/**", "jest.config.*"],
      "outputs": ["coverage/**"],
      "env": ["CI"]
    },
    "test:e2e": {
      "dependsOn": ["build"],
      "inputs": ["e2e/**", "playwright.config.*"],
      "outputs": ["test-results/**"],
      "cache": false
    }
  }
}

Task Dependencies

Dependency Types

json
{
  "tasks": {
    // Depends on own package's dependencies' build first
    "build": {
      "dependsOn": ["^build"]
    },

    // Depends on same package's other tasks
    "test": {
      "dependsOn": ["build", "lint"]
    },

    // Depends on specific package's task
    "deploy": {
      "dependsOn": ["@repo/api#build", "@repo/web#build"]
    },

    // No dependencies - runs in parallel
    "lint": {
      "dependsOn": []
    }
  }
}

Task Graph Visualization

bash
# Visualize task graph
turbo run build --graph

# Output to file
turbo run build --graph=graph.html

# Dry run to see what would execute
turbo run build --dry-run

Caching Strategies

Local Cache Configuration

json
{
  "tasks": {
    "build": {
      "outputs": ["dist/**"],
      "cache": true
    },
    "dev": {
      "cache": false,
      "persistent": true
    }
  }
}

Cache Inputs

json
{
  "tasks": {
    "build": {
      "inputs": [
        "src/**/*.ts",
        "src/**/*.tsx",
        "package.json",
        "tsconfig.json",
        "$TURBO_DEFAULT$"
      ],
      "outputs": ["dist/**"]
    }
  },
  "globalDependencies": [".env.production", "turbo.json"]
}

Environment Variables in Cache

json
{
  "globalEnv": ["CI", "NODE_ENV"],
  "tasks": {
    "build": {
      "env": ["API_URL", "PUBLIC_*"],
      "passThroughEnv": ["AWS_SECRET_KEY"]
    }
  }
}

Remote Caching

Vercel Remote Cache

bash
# Login to Vercel
npx turbo login

# Link to Vercel project
npx turbo link

# Enable remote cache
turbo run build --remote-cache-timeout=60

Self-Hosted Remote Cache

bash
# Using custom remote cache server
TURBO_API="https://cache.mycompany.com" \
TURBO_TOKEN="your-token" \
TURBO_TEAM="my-team" \
turbo run build
json
// turbo.json - Remote cache config
{
  "remoteCache": {
    "signature": true,
    "enabled": true
  }
}

CI Environment Variables

yaml
# GitHub Actions
env:
  TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
  TURBO_TEAM: ${{ vars.TURBO_TEAM }}
  TURBO_REMOTE_ONLY: true

Filtering and Scopes

Filter Syntax

bash
# Run in specific package
turbo run build --filter=@repo/web

# Run in package and dependencies
turbo run build --filter=@repo/web...

# Run in package and dependents
turbo run build --filter=...@repo/ui

# Run in changed packages (since main)
turbo run build --filter=[main]

# Run in changed packages and dependents
turbo run build --filter=...[main]

# Exclude packages
turbo run build --filter=!@repo/docs

# Multiple filters
turbo run build --filter=@repo/web --filter=@repo/api

# Directory-based filter
turbo run build --filter="./apps/*"

# Scope with dependencies
turbo run build --filter=@repo/web^...

Package-Specific Scripts

json
// apps/web/package.json
{
  "name": "@repo/web",
  "scripts": {
    "build": "next build",
    "dev": "next dev --port 3000",
    "lint": "eslint .",
    "test": "vitest"
  }
}

Internal Packages

Package Structure

packages/
├── ui/
│   ├── package.json
│   ├── tsconfig.json
│   └── src/
│       ├── Button.tsx
│       ├── Card.tsx
│       └── index.ts
├── utils/
│   ├── package.json
│   └── src/
│       └── index.ts
└── config/
    ├── eslint/
    │   └── package.json
    └── typescript/
        └── package.json

Internal Package Configuration

json
// packages/ui/package.json
{
  "name": "@repo/ui",
  "version": "0.0.0",
  "private": true,
  "exports": {
    ".": "./src/index.ts",
    "./button": "./src/Button.tsx",
    "./card": "./src/Card.tsx"
  },
  "typesVersions": {
    "*": {
      "*": ["src/*"]
    }
  },
  "scripts": {
    "build": "tsup src/index.ts --format cjs,esm --dts",
    "dev": "tsup src/index.ts --format cjs,esm --dts --watch",
    "lint": "eslint src/"
  },
  "devDependencies": {
    "@repo/typescript-config": "workspace:*",
    "tsup": "^8.0.0",
    "typescript": "^5.0.0"
  }
}

Consuming Internal Packages

json
// apps/web/package.json
{
  "name": "@repo/web",
  "dependencies": {
    "@repo/ui": "workspace:*",
    "@repo/utils": "workspace:*"
  }
}
tsx
// apps/web/src/page.tsx
import { Button, Card } from "@repo/ui";
import { formatDate } from "@repo/utils";

Shared Configurations

Shared TypeScript Config

json
// packages/typescript-config/base.json
{
  "$schema": "https://json.schemastore.org/tsconfig",
  "compilerOptions": {
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "moduleResolution": "bundler",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "declaration": true,
    "declarationMap": true
  }
}
json
// packages/typescript-config/nextjs.json
{
  "$schema": "https://json.schemastore.org/tsconfig",
  "extends": "./base.json",
  "compilerOptions": {
    "lib": ["dom", "dom.iterable", "esnext"],
    "jsx": "preserve",
    "module": "esnext",
    "noEmit": true,
    "plugins": [{ "name": "next" }]
  }
}
json
// apps/web/tsconfig.json
{
  "extends": "@repo/typescript-config/nextjs.json",
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]
    }
  },
  "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
  "exclude": ["node_modules"]
}

Shared ESLint Config

js
// packages/eslint-config/base.js
module.exports = {
  extends: [
    "eslint:recommended",
    "plugin:@typescript-eslint/recommended",
    "prettier",
  ],
  parser: "@typescript-eslint/parser",
  plugins: ["@typescript-eslint"],
  rules: {
    "@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }],
    "@typescript-eslint/no-explicit-any": "warn",
  },
};
js
// packages/eslint-config/react.js
module.exports = {
  extends: [
    "./base.js",
    "plugin:react/recommended",
    "plugin:react-hooks/recommended",
  ],
  plugins: ["react", "react-hooks"],
  settings: {
    react: {   },
  rules: {
    "react/react-in-jsx-scope": "off",
    "react/prop-types": "off",
  },
};

CI/CD Optimization

GitHub Actions with Turborepo

yaml
# .github/workflows/ci.yml
name: CI

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

env:
  TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
  TURBO_TEAM: ${{ vars.TURBO_TEAM }}

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 2

      - uses: pnpm/action-setup@v3
        with:
          
      - uses: actions/setup-node@v4
        with:
          node-          cache: "pnpm"

      - run: pnpm install --frozen-lockfile

      - name: Build
        run: pnpm turbo run build --filter="...[HEAD^1]"

      - name: Test
        run: pnpm turbo run test --filter="...[HEAD^1]"

      - name: Lint
        run: pnpm turbo run lint --filter="...[HEAD^1]"

Affected Package Detection

yaml
# Build only affected packages
- name: Build affected
  run: |
    pnpm turbo run build \
      --filter="...[origin/main]" \
      --concurrency=4

# Deploy specific apps if changed
- name: Deploy web if changed
  run: |
    if pnpm turbo run build --filter=@repo/web...[origin/main] --dry-run | grep -q "@repo/web"; then
      pnpm turbo run deploy --filter=@repo/web
    fi

Parallel Jobs Matrix

yaml
jobs:
  detect-changes:
    runs-on: ubuntu-latest
    outputs:
      packages: ${{ steps.filter.outputs.packages }}
    steps:
      - uses: actions/checkout@v4
      - id: filter
        run: |
          PACKAGES=$(pnpm turbo run build --dry-run=json --filter="[HEAD^1]" | jq -c '[.packages[]]')
          echo "packages=$PACKAGES" >> $GITHUB_OUTPUT

  build:
    needs: detect-changes
    runs-on: ubuntu-latest
    strategy:
      matrix:
        package: ${{ fromJson(needs.detect-changes.outputs.packages) }}
    steps:
      - uses: actions/checkout@v4
      - run: pnpm turbo run build --filter=${{ matrix.package }}

Nx Comparison Notes

Feature Turborepo Nx
Task Caching ✅ Built-in ✅ Built-in
Remote Cache ✅ Vercel/Self-hosted ✅ Nx Cloud
Affected Detection ✅ Git-based ✅ Dependency graph
Code Generation ❌ None ✅ Extensive generators
Plugin Ecosystem ❌ Limited ✅ Rich ecosystem
Learning Curve ✅ Minimal ⚠️ Steeper
Configuration ✅ Simple JSON ⚠️ More complex
Incremental Adoption ✅ Easy ⚠️ More involved

When to Choose Turborepo

  • Simpler monorepo needs
  • Already using Vercel
  • Minimal configuration preferred
  • Incremental adoption from existing setup

When to Choose Nx

  • Need code generators
  • Complex enterprise monorepos
  • Want integrated tooling
  • Need extensive plugin support

Best Practices

Repository Structure

my-monorepo/
├── apps/
│   ├── web/              # Next.js frontend
│   ├── api/              # Express/Fastify backend
│   └── docs/             # Documentation site
├── packages/
│   ├── ui/               # Shared React components
│   ├── utils/            # Shared utilities
│   ├── types/            # Shared TypeScript types
│   └── config/
│       ├── eslint/       # Shared ESLint config
│       └── typescript/   # Shared TS config
├── turbo.json
├── pnpm-workspace.yaml
└── package.json

Performance Tips

bash
# Limit concurrency on CI
turbo run build --concurrency=4

# Use remote cache in CI only
TURBO_REMOTE_ONLY=true turbo run build

# Skip cache for debugging
turbo run build --force

# Prune for Docker builds
turbo prune @repo/api --docker

Docker Optimization

dockerfile
# Dockerfile using turbo prune
FROM node:20-alpine AS builder
RUN apk add --no-cache libc6-compat
WORKDIR /app

# Install turbo globally
RUN npm install -g turbo

# Copy source and prune
COPY . .
RUN turbo prune @repo/api --docker

# Install dependencies
FROM node:20-alpine AS installer
WORKDIR /app
COPY --from=builder /app/out/json/ .
COPY --from=builder /app/out/pnpm-lock.yaml ./pnpm-lock.yaml
RUN corepack enable pnpm && pnpm install --frozen-lockfile

# Build
COPY --from=builder /app/out/full/ .
RUN pnpm turbo run build --filter=@repo/api

# Run
FROM node:20-alpine AS runner
WORKDIR /app
COPY --from=installer /app/apps/api/dist ./dist
CMD ["node", "dist/index.js"]

Versioning and Publishing

json
// package.json
{
  "scripts": {
    "version-packages": "changeset version",
    "publish-packages": "turbo run build --filter='./packages/*' && changeset publish"
  }
}

Troubleshooting

Common Issues

bash
# Clear all caches
turbo run clean
rm -rf node_modules .turbo

# Debug cache misses
turbo run build --summarize

# Check why task ran
turbo run build --dry-run=json | jq '.tasks[] | {name, cache}'

# Verbose logging
turbo run build --verbosity=2

Cache Debugging

bash
# Show cache status
turbo run build --summarize

# Output shows:
# - Tasks: 5 successful, 3 cached
# - Time saved: 45s
# - Cache hit rate: 60%

Quick Reference

Command Description
turbo run build Run build in all packages
turbo run build --filter=web Run in specific package
turbo run build --filter=...[main] Run in changed packages
turbo run build --force Skip cache
turbo run build --dry-run Show what would run
turbo run build --graph Visualize task graph
turbo prune --docker Prune for Docker
turbo login Authenticate for remote cache
turbo link Link to Vercel project

Didn't find tool you were looking for?

Be as detailed as possible for better results