Configure Turborepo for efficient monorepo builds with local
Turborepo caching

Configure Turborepo for efficient monorepo builds with local and remote caching. Use when setting up Turborepo, optimizing build pipelines, or implementing distributed caching.
--- name: turborepo-caching description: Configure Turborepo for efficient monorepo builds with local and remote caching. Use when setting up Turborepo, optimizing build pipelines, or implementing distributed caching. --- # Turborepo Caching Production patterns for Turborepo build optimization. ## When to Use This Skill - Setting up new Turborepo projects - Configuring build pipelines - Implementing remote caching - Optimizing CI/CD performance - Migrating from other monorepo tools - Debugging cache misses
Overview
Configure Turborepo for efficient monorepo builds with local and remote caching. Use when setting up Turborepo, optimizing build pipelines, or implementing distributed caching.
Manual install (npx, ZIP)
Claude Code
npx skills add wshobson/agents --skill turborepo-caching -g -a claude-codeClaude Code (manual)
mkdir -p ~/.claude/skills && curl -sL https://codeload.github.com/wshobson/agents/tar.gz/4236bb91f839 \
| tar -xz -C ~/.claude/skills --strip-components=4 agents-4236bb91f839/plugins/developer-essentials/skills/turborepo-cachingWindows (PowerShell)
New-Item -ItemType Directory -Force "$HOME\.claude\skills" | Out-Null; Invoke-WebRequest "https://m.agentalley.io/zip/73adba104d347e4afceb/turborepo-caching.zip" -OutFile "$env:TEMP\turborepo-caching.zip"; Expand-Archive "$env:TEMP\turborepo-caching.zip" -DestinationPath "$HOME\.claude\skills" -ForceClaude.ai / desktop
Codex, Cursor, other agents
Connects with Agent Alley MCP · agentalley.io/mcp
Files included
SKILL.md7.9 KB
Read the files here
wshobson/
SKILL.md
Show the full text371 lines
Turborepo Caching
Production patterns for Turborepo build optimization.
When to Use This Skill
- Setting up new Turborepo projects
- Configuring build pipelines
- Implementing remote caching
- Optimizing CI/CD performance
- Migrating from other monorepo tools
- Debugging cache misses
Core Concepts
1. Turborepo Architecture
Workspace Root/
├── apps/
│ ├── web/
│ │ └── package.json
│ └── docs/
│ └── package.json
├── packages/
│ ├── ui/
│ │ └── package.json
│ └── config/
│ └── package.json
├── turbo.json
└── package.json
2. Pipeline Concepts
| Concept | Description |
|---|---|
| dependsOn | Tasks that must complete first |
| cache | Whether to cache outputs |
| outputs | Files to cache |
| inputs | Files that affect cache key |
| persistent | Long-running tasks (dev servers) |
Templates
Template 1: turbo.json Configuration
{
"$schema": "https://turbo.build/schema.json",
"globalDependencies": [".env", ".env.local"],
"globalEnv": ["NODE_ENV", "VERCEL_URL"],
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**", ".next/**", "!.next/cache/**"],
"env": ["API_URL", "NEXT_PUBLIC_*"]
},
"test": {
"dependsOn": ["build"],
"outputs": ["coverage/**"],
"inputs": ["src/**/*.tsx", "src/**/*.ts", "test/**/*.ts"]
},
"lint": {
"outputs": [],
"cache": true
},
"typecheck": {
"dependsOn": ["^build"],
"outputs": []
},
"dev": {
"cache": false,
"persistent": true
},
"clean": {
"cache": false
}
}
}
Template 2: Package-Specific Pipeline
// apps/web/turbo.json
{
"$schema": "https://turbo.build/schema.json",
"extends": ["//"],
"pipeline": {
"build": {
"outputs": [".next/**", "!.next/cache/**"],
"env": ["NEXT_PUBLIC_API_URL", "NEXT_PUBLIC_ANALYTICS_ID"]
},
"test": {
"outputs": ["coverage/**"],
"inputs": ["src/**", "tests/**", "jest.config.js"]
}
}
}
Template 3: Remote Caching with Vercel
# Login to Vercel
npx turbo login
# Link to Vercel project
npx turbo link
# Run with remote cache
turbo build --remote-only
# CI environment variables
TURBO_TOKEN=your-token
TURBO_TEAM=your-team
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main]
pull_request:
env:
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: "npm"
- name: Install dependencies
run: npm ci
- name: Build
run: npx turbo build --filter='...[origin/main]'
- name: Test
run: npx turbo test --filter='...[origin/main]'
Template 4: Self-Hosted Remote Cache
// Custom remote cache server (Express)
import express from "express";
import { createReadStream, createWriteStream } from "fs";
import { mkdir } from "fs/promises";
import { join } from "path";
const app = express();
const CACHE_DIR = "./cache";
// Get artifact
app.get("/v8/artifacts/:hash", async (req, res) => {
const { hash } = req.params;
const team = req.query.teamId || "default";
const filePath = join(CACHE_DIR, team, hash);
try {
const stream = createReadStream(filePath);
stream.pipe(res);
} catch {
res.status(404).send("Not found");
}
});
// Put artifact
app.put("/v8/artifacts/:hash", async (req, res) => {
const { hash } = req.params;
const team = req.query.teamId || "default";
const dir = join(CACHE_DIR, team);
const filePath = join(dir, hash);
await mkdir(dir, { recursive: true });
const stream = createWriteStream(filePath);
req.pipe(stream);
stream.on("finish", () => {
res.json({
urls: [`${req.protocol}://${req.get("host")}/v8/artifacts/${hash}`],
});
});
});
// Check artifact exists
app.head("/v8/artifacts/:hash", async (req, res) => {
const { hash } = req.params;
const team = req.query.teamId || "default";
const filePath = join(CACHE_DIR, team, hash);
try {
await fs.access(filePath);
res.status(200).end();
} catch {
res.status(404).end();
}
});
app.listen(3000);
// turbo.json for self-hosted cache
{
"remoteCache": {
"signature": false
}
}
# Use self-hosted cache
turbo build --api="http://localhost:3000" --token="my-token" --team="my-team"
Template 5: Filtering and Scoping
# Build specific package
turbo build --filter=@myorg/web
# Build package and its dependencies
turbo build --filter=@myorg/web...
# Build package and its dependents
turbo build --filter=...@myorg/ui
# Build changed packages since main
turbo build --filter='...[origin/main]'
# Build packages in directory
turbo build --filter='./apps/*'
# Combine filters
turbo build --filter=@myorg/web --filter=@myorg/docs
# Exclude package
turbo build --filter='!@myorg/docs'
# Include dependencies of changed
turbo build --filter='...[HEAD^1]...'
Template 6: Advanced Pipeline Configuration
{
"$schema": "https://turbo.build/schema.json",
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**"],
"inputs": ["$TURBO_DEFAULT$", "!**/*.md", "!**/*.test.*"]
},
"test": {
"dependsOn": ["^build"],
"outputs": ["coverage/**"],
"inputs": ["src/**", "tests/**", "*.config.*"],
"env": ["CI", "NODE_ENV"]
},
"test:e2e": {
"dependsOn": ["build"],
"outputs": [],
"cache": false
},
"deploy": {
"dependsOn": ["build", "test", "lint"],
"outputs": [],
"cache": false
},
"db:generate": {
"cache": false
},
"db:push": {
"cache": false,
"dependsOn": ["db:generate"]
},
"@myorg/web#build": {
"dependsOn": ["^build", "@myorg/db#db:generate"],
"outputs": [".next/**"],
"env": ["NEXT_PUBLIC_*"]
}
}
}
Template 7: Root package.json Setup
{
"name": "my-turborepo",
"private": true,
"workspaces": ["apps/*", "packages/*"],
"scripts": {
"build": "turbo build",
"dev": "turbo dev",
"lint": "turbo lint",
"test": "turbo test",
"clean": "turbo clean && rm -rf node_modules",
"format": "prettier --write \"**/*.{ts,tsx,md}\"",
"changeset": "changeset",
"version-packages": "changeset version",
"release": "turbo build --filter=./packages/* && changeset publish"
},
"devDependencies": {
"turbo": "^1.10.0",
"prettier": "^3.0.0",
"@changesets/cli": "^2.26.0"
},
"packageManager": "[email protected]"
}
Debugging Cache
# Dry run to see what would run
turbo build --dry-run
# Verbose output with hashes
turbo build --verbosity=2
# Show task graph
turbo build --graph
# Force no cache
turbo build --force
# Show cache status
turbo build --summarize
# Debug specific task
TURBO_LOG_VERBOSITY=debug turbo build --filter=@myorg/web
Best Practices
Do's
- Define explicit inputs - Avoid cache invalidation
- Use workspace protocol -
"@myorg/ui": "workspace:*" - Enable remote caching - Share across CI and local
- Filter in CI - Build only affected packages
- Cache build outputs - Not source files
Don'ts
- Don't cache dev servers - Use
persistent: true - Don't include secrets in env - Use runtime env vars
- Don't ignore dependsOn - Causes race conditions
- Don't over-filter - May miss dependencies
| 1 | |
| 2 | name turborepo-caching |
| 3 | description Configure Turborepo for efficient monorepo builds with local and remote caching. Use when setting up Turborepo, optimizing build pipelines, or implementing distributed caching. |
| 4 | |
| 5 | |
| 6 | # Turborepo Caching |
| 7 | |
| 8 | Production patterns for Turborepo build optimization. |
| 9 | |
| 10 | ## When to Use This Skill |
| 11 | |
| 12 | Setting up new Turborepo projects |
| 13 | Configuring build pipelines |
| 14 | Implementing remote caching |
| 15 | Optimizing CI/CD performance |
| 16 | Migrating from other monorepo tools |
| 17 | Debugging cache misses |
| 18 | |
| 19 | ## Core Concepts |
| 20 | |
| 21 | ### 1. Turborepo Architecture |
| 22 | |
| 23 | |
| 24 | Workspace Root/ |
| 25 | ├── apps/ |
| 26 | │ ├── web/ |
| 27 | │ │ └── package.json |
| 28 | │ └── docs/ |
| 29 | │ └── package.json |
| 30 | ├── packages/ |
| 31 | │ ├── ui/ |
| 32 | │ │ └── package.json |
| 33 | │ └── config/ |
| 34 | │ └── package.json |
| 35 | ├── turbo.json |
| 36 | └── package.json |
| 37 | |
| 38 | |
| 39 | ### 2. Pipeline Concepts |
| 40 | |
| 41 | | Concept | Description | |
| 42 | | -------------- | -------------------------------- | |
| 43 | | **dependsOn** | Tasks that must complete first | |
| 44 | | **cache** | Whether to cache outputs | |
| 45 | | **outputs** | Files to cache | |
| 46 | | **inputs** | Files that affect cache key | |
| 47 | | **persistent** | Long-running tasks (dev servers) | |
| 48 | |
| 49 | ## Templates |
| 50 | |
| 51 | ### Template 1: turbo.json Configuration |
| 52 | |
| 53 | |
| 54 | { |
| 55 | "$schema": "https://turbo.build/schema.json", |
| 56 | "globalDependencies": [".env", ".env.local"], |
| 57 | "globalEnv": ["NODE_ENV", "VERCEL_URL"], |
| 58 | "pipeline": { |
| 59 | "build": { |
| 60 | "dependsOn": ["^build"], |
| 61 | "outputs": ["dist/**", ".next/**", "!.next/cache/**"], |
| 62 | "env": ["API_URL", "NEXT_PUBLIC_*"] |
| 63 | }, |
| 64 | "test": { |
| 65 | "dependsOn": ["build"], |
| 66 | "outputs": ["coverage/**"], |
| 67 | "inputs": ["src/**/*.tsx", "src/**/*.ts", "test/**/*.ts"] |
| 68 | }, |
| 69 | "lint": { |
| 70 | "outputs": [], |
| 71 | "cache": true |
| 72 | }, |
| 73 | "typecheck": { |
| 74 | "dependsOn": ["^build"], |
| 75 | "outputs": [] |
| 76 | }, |
| 77 | "dev": { |
| 78 | "cache": false, |
| 79 | "persistent": true |
| 80 | }, |
| 81 | "clean": { |
| 82 | "cache": false |
| 83 | } |
| 84 | } |
| 85 | } |
| 86 | |
| 87 | |
| 88 | ### Template 2: Package-Specific Pipeline |
| 89 | |
| 90 | |
| 91 | // apps/web/turbo.json |
| 92 | { |
| 93 | "$schema": "https://turbo.build/schema.json", |
| 94 | "extends": ["//"], |
| 95 | "pipeline": { |
| 96 | "build": { |
| 97 | "outputs": [".next/**", "!.next/cache/**"], |
| 98 | "env": ["NEXT_PUBLIC_API_URL", "NEXT_PUBLIC_ANALYTICS_ID"] |
| 99 | }, |
| 100 | "test": { |
| 101 | "outputs": ["coverage/**"], |
| 102 | "inputs": ["src/**", "tests/**", "jest.config.js"] |
| 103 | } |
| 104 | } |
| 105 | } |
| 106 | |
| 107 | |
| 108 | ### Template 3: Remote Caching with Vercel |
| 109 | |
| 110 | |
| 111 | # Login to Vercel |
| 112 | npx turbo login |
| 113 | |
| 114 | # Link to Vercel project |
| 115 | npx turbo link |
| 116 | |
| 117 | # Run with remote cache |
| 118 | turbo build --remote-only |
| 119 | |
| 120 | # CI environment variables |
| 121 | TURBO_TOKEN=your-token |
| 122 | TURBO_TEAM=your-team |
| 123 | |
| 124 | |
| 125 | |
| 126 | # .github/workflows/ci.yml |
| 127 | name: CI |
| 128 | |
| 129 | on: |
| 130 | push: |
| 131 | branches: [main] |
| 132 | pull_request: |
| 133 | |
| 134 | env: |
| 135 | TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} |
| 136 | TURBO_TEAM: ${{ vars.TURBO_TEAM }} |
| 137 | |
| 138 | jobs: |
| 139 | build: |
| 140 | runs-on: ubuntu-latest |
| 141 | steps: |
| 142 | - uses: actions/checkout@v4 |
| 143 | |
| 144 | - uses: actions/setup-node@v4 |
| 145 | with: |
| 146 | node-version: 20 |
| 147 | cache: "npm" |
| 148 | |
| 149 | - name: Install dependencies |
| 150 | run: npm ci |
| 151 | |
| 152 | - name: Build |
| 153 | run: npx turbo build --filter='...[origin/main]' |
| 154 | |
| 155 | - name: Test |
| 156 | run: npx turbo test --filter='...[origin/main]' |
| 157 | |
| 158 | |
| 159 | ### Template 4: Self-Hosted Remote Cache |
| 160 | |
| 161 | |
| 162 | // Custom remote cache server (Express) |
| 163 | import express from "express"; |
| 164 | import { createReadStream, createWriteStream } from "fs"; |
| 165 | import { mkdir } from "fs/promises"; |
| 166 | import { join } from "path"; |
| 167 | |
| 168 | const app = express(); |
| 169 | const CACHE_DIR = "./cache"; |
| 170 | |
| 171 | // Get artifact |
| 172 | app.get("/v8/artifacts/:hash", async (req, res) => { |
| 173 | const { hash } = req.params; |
| 174 | const team = req.query.teamId || "default"; |
| 175 | const filePath = join(CACHE_DIR, team, hash); |
| 176 | |
| 177 | try { |
| 178 | const stream = createReadStream(filePath); |
| 179 | stream.pipe(res); |
| 180 | } catch { |
| 181 | res.status(404).send("Not found"); |
| 182 | } |
| 183 | }); |
| 184 | |
| 185 | // Put artifact |
| 186 | app.put("/v8/artifacts/:hash", async (req, res) => { |
| 187 | const { hash } = req.params; |
| 188 | const team = req.query.teamId || "default"; |
| 189 | const dir = join(CACHE_DIR, team); |
| 190 | const filePath = join(dir, hash); |
| 191 | |
| 192 | await mkdir(dir, { recursive: true }); |
| 193 | |
| 194 | const stream = createWriteStream(filePath); |
| 195 | req.pipe(stream); |
| 196 | |
| 197 | stream.on("finish", () => { |
| 198 | res.json({ |
| 199 | urls: [`${req.protocol}://${req.get("host")}/v8/artifacts/${hash}`], |
| 200 | }); |
| 201 | }); |
| 202 | }); |
| 203 | |
| 204 | // Check artifact exists |
| 205 | app.head("/v8/artifacts/:hash", async (req, res) => { |
| 206 | const { hash } = req.params; |
| 207 | const team = req.query.teamId || "default"; |
| 208 | const filePath = join(CACHE_DIR, team, hash); |
| 209 | |
| 210 | try { |
| 211 | await fs.access(filePath); |
| 212 | res.status(200).end(); |
| 213 | } catch { |
| 214 | res.status(404).end(); |
| 215 | } |
| 216 | }); |
| 217 | |
| 218 | app.listen(3000); |
| 219 | |
| 220 | |
| 221 | |
| 222 | // turbo.json for self-hosted cache |
| 223 | { |
| 224 | "remoteCache": { |
| 225 | "signature": false |
| 226 | } |
| 227 | } |
| 228 | |
| 229 | |
| 230 | |
| 231 | # Use self-hosted cache |
| 232 | turbo build --api="http://localhost:3000" --token="my-token" --team="my-team" |
| 233 | |
| 234 | |
| 235 | ### Template 5: Filtering and Scoping |
| 236 | |
| 237 | |
| 238 | # Build specific package |
| 239 | turbo build --filter=@myorg/web |
| 240 | |
| 241 | # Build package and its dependencies |
| 242 | turbo build --filter=@myorg/web... |
| 243 | |
| 244 | # Build package and its dependents |
| 245 | turbo build --filter=...@myorg/ui |
| 246 | |
| 247 | # Build changed packages since main |
| 248 | turbo build --filter='...[origin/main]' |
| 249 | |
| 250 | # Build packages in directory |
| 251 | turbo build --filter='./apps/*' |
| 252 | |
| 253 | # Combine filters |
| 254 | turbo build --filter=@myorg/web --filter=@myorg/docs |
| 255 | |
| 256 | # Exclude package |
| 257 | turbo build --filter='!@myorg/docs' |
| 258 | |
| 259 | # Include dependencies of changed |
| 260 | turbo build --filter='...[HEAD^1]...' |
| 261 | |
| 262 | |
| 263 | ### Template 6: Advanced Pipeline Configuration |
| 264 | |
| 265 | |
| 266 | { |
| 267 | "$schema": "https://turbo.build/schema.json", |
| 268 | "pipeline": { |
| 269 | "build": { |
| 270 | "dependsOn": ["^build"], |
| 271 | "outputs": ["dist/**"], |
| 272 | "inputs": ["$TURBO_DEFAULT$", "!**/*.md", "!**/*.test.*"] |
| 273 | }, |
| 274 | "test": { |
| 275 | "dependsOn": ["^build"], |
| 276 | "outputs": ["coverage/**"], |
| 277 | "inputs": ["src/**", "tests/**", "*.config.*"], |
| 278 | "env": ["CI", "NODE_ENV"] |
| 279 | }, |
| 280 | "test:e2e": { |
| 281 | "dependsOn": ["build"], |
| 282 | "outputs": [], |
| 283 | "cache": false |
| 284 | }, |
| 285 | "deploy": { |
| 286 | "dependsOn": ["build", "test", "lint"], |
| 287 | "outputs": [], |
| 288 | "cache": false |
| 289 | }, |
| 290 | "db:generate": { |
| 291 | "cache": false |
| 292 | }, |
| 293 | "db:push": { |
| 294 | "cache": false, |
| 295 | "dependsOn": ["db:generate"] |
| 296 | }, |
| 297 | "@myorg/web#build": { |
| 298 | "dependsOn": ["^build", "@myorg/db#db:generate"], |
| 299 | "outputs": [".next/**"], |
| 300 | "env": ["NEXT_PUBLIC_*"] |
| 301 | } |
| 302 | } |
| 303 | } |
| 304 | |
| 305 | |
| 306 | ### Template 7: Root package.json Setup |
| 307 | |
| 308 | |
| 309 | { |
| 310 | "name": "my-turborepo", |
| 311 | "private": true, |
| 312 | "workspaces": ["apps/*", "packages/*"], |
| 313 | "scripts": { |
| 314 | "build": "turbo build", |
| 315 | "dev": "turbo dev", |
| 316 | "lint": "turbo lint", |
| 317 | "test": "turbo test", |
| 318 | "clean": "turbo clean && rm -rf node_modules", |
| 319 | "format": "prettier --write \"**/*.{ts,tsx,md}\"", |
| 320 | "changeset": "changeset", |
| 321 | "version-packages": "changeset version", |
| 322 | "release": "turbo build --filter=./packages/* && changeset publish" |
| 323 | }, |
| 324 | "devDependencies": { |
| 325 | "turbo": "^1.10.0", |
| 326 | "prettier": "^3.0.0", |
| 327 | "@changesets/cli": "^2.26.0" |
| 328 | }, |
| 329 | "packageManager": "[email protected]" |
| 330 | } |
| 331 | |
| 332 | |
| 333 | ## Debugging Cache |
| 334 | |
| 335 | |
| 336 | # Dry run to see what would run |
| 337 | turbo build --dry-run |
| 338 | |
| 339 | # Verbose output with hashes |
| 340 | turbo build --verbosity=2 |
| 341 | |
| 342 | # Show task graph |
| 343 | turbo build --graph |
| 344 | |
| 345 | # Force no cache |
| 346 | turbo build --force |
| 347 | |
| 348 | # Show cache status |
| 349 | turbo build --summarize |
| 350 | |
| 351 | # Debug specific task |
| 352 | TURBO_LOG_VERBOSITY=debug turbo build --filter=@myorg/web |
| 353 | |
| 354 | |
| 355 | ## Best Practices |
| 356 | |
| 357 | ### Do's |
| 358 | |
| 359 | **Define explicit inputs** - Avoid cache invalidation |
| 360 | **Use workspace protocol** - `"@myorg/ui": "workspace:*"` |
| 361 | **Enable remote caching** - Share across CI and local |
| 362 | **Filter in CI** - Build only affected packages |
| 363 | **Cache build outputs** - Not source files |
| 364 | |
| 365 | ### Don'ts |
| 366 | |
| 367 | **Don't cache dev servers** - Use `persistent: true` |
| 368 | **Don't include secrets in env** - Use runtime env vars |
| 369 | **Don't ignore dependsOn** - Causes race conditions |
| 370 | **Don't over-filter** - May miss dependencies |
| 371 |
Details
- Updated
- Sep 21, 2026
- License
- MIT
- Stars on the repo
- ★ 39,857
- Checked
- Source
- wshobson/agents ↗
- Creator
- wshobson · 179 listings
Similar skills
See all →
SkillFrom a business idea, app ideaAzure app onboard
SkillAssess whether source code is ready to deploy to AzureAzure App Onboard Prereq — Repository Evaluation
SkillConfigure Azure API Management as an AI Gateway for AIAzure aigateway
SkillQuery and analyze data in Azure Data Explorer using KQLAzure Data Explorer (Kusto) Query & Analytics
MCPRun any server from the Docker MCP Catalog in its ownDocker MCP gateway
MCPOpen infrastructure for AI agents to discover and pay eachElisymMore from wshobson
SkillMaster C/.NET backend development patterns for building
SkillImplement WCAG 2.2 compliant interfaces with mobileAccessibility compliance
SkillMaster Material Design 3 and Jetpack Compose patternsAndroid Mobile Design
SkillMigrate from AngularJS to Angular using hybrid modeAngular migration
SkillUnderstand anti-reversing, obfuscationAnti reversing techniques
SkillBuild production Apache Airflow DAGs with best practicesApache Airflow DAG Patterns