Configure and troubleshoot Turborepo repositories. Use when working with turbo.json, task pipelines, caching, Remote Cache, the turbo CLI, filtering, environment variables, package boundaries, monorepo structure, or CI workflows.
---
name: turborepo
description: Configure and troubleshoot Turborepo repositories. Use when working with turbo.json, task pipelines, caching, Remote Cache, the turbo CLI, filtering, environment variables, package boundaries, monorepo structure, or CI workflows.
---
# Turborepo
The complete Turborepo documentation ships inside the installed `turbo` package. Do not rely on this skill for framework guidance. Always read the bundled docs, which match the installed version exactly.
Start with:
```text
node_modules/turbo/docs/README.md
```
Use that task index to choose the smallest relevant documentation page. Read it before changing Turborepo configuration, package scripts, or CI workflows.
If the package manager uses a non-flat `node_modules` layout or a workspace link, resolve the package location first:
```sh
node -p "require.resolve('turbo/package.json')"
```
Then read `docs/README.md` relative to the resolved package directory.
If `turbo` is not installed, inspect the repository's package manager and existing version constraints before adding it. After installation, use the bundled docs rather than guidance for a different release.
don't have the plugin yet? install it then click "run inline in claude" again.
restructured raw guide into implexa's six-part format with explicit decision logic, edge cases, environment setup guidance, and concrete success criteria while preserving original turborepo philosophy.
Turborepo is a build system for JavaScript/TypeScript monorepos that caches task outputs and runs tasks in parallel based on dependency graphs. use this skill when configuring tasks, creating packages, setting up monorepos, sharing code between apps, running changed/affected packages, debugging cache, or structuring apps/packages directories. turborepo lets you define what tasks depend on what, which outputs to cache, which environment variables matter, and how to parallelize work without redundant builds.
turbo.json at the repo root and package.json files in each package.package.json with scripts defined. root package.json should only delegate to turbo run, not contain task logic.dependsOn relationships, cache outputs, environment variables, global settings.--affected flag). requires git initialized and remote configured (typically origin/main as default branch).TURBO_TOKEN and TURBO_TEAM env vars (for Vercel) or custom remoteCache config in turbo.json.minimum viable turbo.json:
{
"$schema": "https://turborepo.org/schema.json",
"tasks": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**", ".next/**"]
},
"lint": {},
"test": {
"dependsOn": ["build"]
}
}
}
enable remote cache (Vercel):
TURBO_TOKEN env var (from turbo.dev login)TURBO_TEAM env var (your team slug)enable remote cache (custom):
{
"remoteCache": {
"apiUrl": "https://my-cache.example.com",
"signature": true
}
}
git setup:
git to compare branches. requires git initialized and default branch configured (usually origin/main).--affected to work, git must know what changed relative to the default branch. if git is missing, --affected fails.input: desired monorepo layout (which packages are apps, which are libraries/shared code).
output: directory structure with each package containing package.json and source files.
apps/ directory for applications (Next.js, Remix, Express servers, etc.).packages/ directory for shared libraries (UI components, utilities, types, validators, etc.).package.json in each app and package with a name (e.g., "name": "@repo/web", "name": "@repo/ui").package.json (e.g., "build": "tsc", "lint": "eslint .", "test": "vitest").package.json. only add delegation scripts like "build": "turbo run build".example structure:
my-monorepo/
├── turbo.json
├── package.json (root - no task logic)
├── apps/
│ ├── web/
│ │ ├── package.json (scripts: build, dev, test, lint)
│ │ └── src/
│ ├── api/
│ │ ├── package.json (scripts: build, test, lint)
│ │ └── src/
├── packages/
│ ├── ui/
│ │ ├── package.json (scripts: build, lint)
│ │ └── src/
│ ├── types/
│ │ ├── package.json (scripts: build)
│ │ └── src/
input: which packages import from which other packages.
output: package.json dependency declarations and turbo.json dependsOn rules.
package.json, add workspace dependencies to other packages. example:{
"dependencies": {
"@repo/types": "workspace:*",
"@repo/ui": "workspace:*"
}
}
workspace:* protocol (pnpm, Yarn modern, bun) or just "*" (npm with workspaces field in root package.json).package.json. if a package imports from another but doesn't declare it, turbo's ^build won't order tasks correctly.example: if apps/web imports from @repo/types and @repo/ui, it must declare both:
{
"name": "@repo/web",
"dependencies": {
"@repo/types": "workspace:*",
"@repo/ui": "workspace:*"
}
}
input: which tasks exist in packages and what their dependencies are.
output: turbo.json with task definitions, dependsOn relationships, outputs, and environment variables.
tasks object with one entry per task type (build, lint, test, dev, etc.).dependsOn: ["^build"] to run builds in dependencies first.dependsOn: ["build"] or dependsOn: ["^build"] as needed.outputs key for any task that writes files to disk (build, generate, etc.). this tells turbo what to cache.env key if the task reads environment variables (e.g., API_URL, DATABASE_URL). turborepo hashes these to invalidate cache.inputs key if the task should re-run when specific files change (e.g., .env, turbo.json).cache: false and persistent: true for long-running tasks (dev servers, watchers).example turbo.json:
{
"$schema": "https://turborepo.org/schema.json",
"tasks": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**", ".next/**", "!.next/cache/**"],
"env": ["NODE_ENV"]
},
"lint": {
"outputs": []
},
"test": {
"dependsOn": ["build"],
"outputs": ["coverage/**"]
},
"dev": {
"cache": false,
"persistent": true
}
}
}
input: which environment variables each task needs to read.
output: env keys in task definitions and/or global globalEnv.
globalEnv at root level.env key in the task definition..env files, add inputs: ["$TURBO_DEFAULT$", ".env", ".env.*"] to invalidate cache when those files change. (turborepo does not load .env files; your framework does. but turbo needs to know about changes.).env files in monorepos. instead, create .env files in specific packages that need them.GITHUB_TOKEN), use globalPassThroughEnv to pass them without invalidating cache unnecessarily.example:
{
"globalEnv": ["NODE_ENV"],
"tasks": {
"build": {
"env": ["API_URL", "DATABASE_URL"],
"inputs": ["$TURBO_DEFAULT$", ".env", ".env.local"]
}
}
}
input: which packages to run tasks in (all, changed, specific names, etc.).
output: task execution with correct filtering and caching.
turbo run build.turbo run build --affected.turbo run build --filter=web.turbo run build --filter=...web.turbo run build --filter=web....turbo run (not shorthand turbo). in CI pipelines, always use turbo run.turbo build shorthand is acceptable but turbo run build is more explicit.examples:
# all packages
turbo run build
# changed packages + dependents
turbo run build --affected
# specific package
turbo run build --filter=@repo/web
# package + dependencies
turbo run build --filter=@repo/web...
# package + dependents
turbo run build --filter=...@repo/web
input: cache misses, unexpected rebuilds, or performance issues.
output: corrected turbo.json or resolved cache problems.
turbo run build --dry to see what turbo plans to run without executing.turbo run build --summarize to see cache hit/miss reasons.outputs key missing or wrong? (file-producing tasks must declare outputs)env missing a variable that affects the build?inputs missing a file that should invalidate cache (e.g., .env, package.json)?globalDependencies too broad, invalidating all tasks?env.turbo run build --force.cache: false in turbo.json.input: CI provider (GitHub Actions, GitLab CI, etc.) and whether to use remote cache.
output: CI workflow that runs turbo with --affected and remote cache.
turbo run build --affected to run only changed packages.TURBO_TOKEN and TURBO_TEAM (or custom remoteCache config).CI=true) are auto-detected by turbo and skip local caching..github/workflows/ci.yml:name: CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- run: turbo run build --affected
- run: turbo run lint --affected
- run: turbo run test --affected
turbo-ignore (turbo's built-in tool for detecting which packages changed).input: which packages should be allowed to import from which other packages (architectural rules).
output: boundary configuration in turbo.json and CI checks.
boundaries key to root turbo.json with rules.package.json (e.g., "turbo": { "tags": ["scope:ui", "type:library"] }).turbo boundaries in CI to check for violations.example:
{
"boundaries": {
"rules": [
{
"from": ["tag:scope:api"],
"to": ["tag:scope:ui"],
"allow": false
}
]
}
}
then add an outputs key listing the files or globs (e.g., "outputs": ["dist/**"]). without this, turbo caches nothing and re-runs the task every time.
else (task only outputs to stdout or is a check/linter), leave outputs empty or omit it.
then add an env key listing the variables (e.g., "env": ["API_URL", "DATABASE_URL"]). turborepo hashes these to invalidate cache when they change.
else (task is deterministic and doesn't read env), omit env.
then add dependsOn: ["^build"] to run builds in dependencies first.
else (task is standalone or only needs source), omit or use dependsOn: [].
then use turbo run build --affected. turborepo detects which files changed relative to the default branch and runs only affected packages plus their dependents.
else (run everything), use turbo run build with no filter.
then set cache: false and persistent: true. turborepo will not cache the output and will keep the process running.
else (cacheable task), omit these keys (defaults are cache: true, persistent: false).
then move variables to package-specific .env files and remove the root .env. if you must share variables, use globalEnv to be explicit.
else (each package has its own .env as needed), no change needed.
cd apps/web && npm run build && cd ../api && npm run build)then refactor to use turbo run with proper dependsOn relationships. turbo orchestrates the order; you don't need manual sequencing.
else (task delegates to turbo), no change needed.
turbo shorthand in package.json or CI scriptsthen change to turbo run <task>. the shorthand is only for interactive terminal commands.
else (using turbo run in scripts), no change needed.
then use Package Configurations (create a turbo.json in each package) instead of cluttering root turbo.json with package#task overrides.
else (all packages share the same task config), keep config in root turbo.json.
then use a Transit Node pattern: define an empty task (e.g., transit) with dependsOn: ["^transit"] and have other tasks depend on it. this creates dependency relationships without requiring built outputs.
else (standard sequential execution), use dependsOn: ["^build"] directly.
then add them to globalPassThroughEnv so they're passed through without invalidating cache unnecessarily.
else (no special CI variables), omit this key.
success looks like:
turbo.json is valid JSON with no syntax errors. it must contain at minimum a tasks object with task definitions.
all packages have package.json files with:
name field (e.g., "@repo/web")scripts field with task definitions (e.g., "build": "tsc", "lint": "eslint .")"dependencies": { "@repo/ui": "workspace:*" })root package.json contains only:
"workspaces": ["apps/*", "packages/*"])turbo run <task> (e.g., "build": "turbo run build")turbo.json task definitions include:
dependsOn for build tasks (e.g., ["^build"]) or empty array/omitted if standaloneoutputs for file-producing tasks (globs like ["dist/**"])env if the task reads environment variablesinputs if the task should re-run when specific files changecache: false and persistent: true for long-running tasks onlyenvironment variables are declared in env or globalEnv keys, not left implicit. .env files are in individual packages, not at the root.
filtering works correctly: turbo run build --affected detects only changed packages and their dependents. turbo run build --filter=web runs only the specified package.
cache is working: running the same task twice produces "cache hit" output (visible with --summarize). removing cache with --force and re-running completes successfully.
CI integration is set up with remote cache enabled (if using Vercel or custom backend) and --affected flag used to optimize CI runs.
no task logic in root package.json. all scripts delegate to turbo (e.g., "build": "turbo run build").
no relative paths (../) in turbo.json inputs/outputs. use $TURBO_ROOT$ instead.
the user knows the skill worked when:
turbo run build completes successfully and outputs "cache hit" on the second run without changing any code.
turbo run build --affected runs only the packages that changed plus their dependents, not the entire monorepo. visible in the output log showing filtered package list.
turbo run lint && turbo run test execute in parallel (if configured to do so) and complete faster than sequential runs.
environment variable changes (e.g., modifying API_URL in .env) cause the cache to invalidate and tasks to re-run. visible with --summarize showing cache miss reason is env change.
CI passes with --affected flag and completes faster than running all tasks. remote cache is populated (visible in Vercel dashboard or custom cache logs).
turbo boundaries check (if configured) catches invalid imports between packages and fails CI.
no "task not found" errors when running turbo run <taskname>. all packages have the script defined in their package.json.
dev servers and watchers don't hang when using cache: false and persistent: true. processes stay alive and respond to file changes.
--filter syntax works intuitively: --filter=web runs web, --filter=...web runs web and its dependents, --filter=web... runs web and its dependencies.
monorepo structure is clear: apps/ contains applications, packages/ contains libraries, no shared code inside an app, no circular imports.