Turborepo Reference: Task Caching, Remote Cache, Filtering & Monorepo Packages
Turborepo is a high-performance build system for JavaScript/TypeScript monorepos. Its core value: task caching — it hashes inputs (source files, env vars, dependencies) and skips tasks whose inputs haven’t changed. Run turbo build in CI after a minor change, and only the affected packages rebuild. Remote caching with Vercel or a self-hosted cache server extends this across machines and CI runs. The key file is turbo.json — it defines which tasks exist, their dependencies, and what to cache.
1. Workspace Setup & turbo.json
Initialize monorepo, workspace config, turbo.json tasks, and package.json scripts
# Create new monorepo:
npx create-turbo@latest
# Or add Turborepo to an existing monorepo:
npx turbo@latest init
# Monorepo structure:
# .
# ├── turbo.json ← task definitions
# ├── package.json ← workspace root
# ├── packages/
# │ ├── ui/ ← shared component library
# │ ├── eslint-config/ ← shared ESLint config
# │ └── tsconfig/ ← shared TypeScript config
# └── apps/
# ├── web/ ← Next.js app
# └── api/ ← Express/Hono API
# package.json (root):
{
"name": "my-monorepo",
"private": true,
"workspaces": ["apps/*", "packages/*"],
"scripts": {
"build": "turbo build",
"dev": "turbo dev",
"lint": "turbo lint",
"test": "turbo test",
"type-check": "turbo type-check"
},
"devDependencies": {
"turbo": "latest"
}
}
# turbo.json — task pipeline:
{
"$schema": "https://turbo.build/schema.json",
"tasks": {
"build": {
"dependsOn": ["^build"], // ^ = run deps' build first (topological)
"outputs": [".next/**", "dist/**", "build/**"],
"cache": true
},
"dev": {
"cache": false, // never cache dev servers
"persistent": true // long-running process
},
"lint": {
"dependsOn": [], // can run in parallel, no deps
"outputs": []
},
"test": {
"dependsOn": ["build"], // test after build
"outputs": ["coverage/**"],
"cache": true
},
"type-check": {
"dependsOn": ["^build"],
"outputs": []
}
}
}
2. Task Caching & Inputs
How caching works, customising inputs, env var inclusion, and cache hit behaviour
// Turborepo caches based on a hash of:
// - Source files in the package (respects .gitignore)
// - package.json + lockfile
// - turbo.json task config
// - Environment variables listed in "env"
// - Output of dependent tasks
// Cache a task with specific inputs:
{
"tasks": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**"],
"inputs": ["src/**", "!src/**/*.test.ts"], // exclude test files from build hash
"env": ["NODE_ENV", "API_URL"], // include env vars in hash
"passThroughEnv": ["CI", "VERCEL"] // available to task but not in hash
},
"test": {
"outputs": ["coverage/**"],
"inputs": ["src/**", "tests/**", "jest.config.*"],
"env": ["CI"]
}
}
}
// Cache outputs are stored in:
// .turbo/cache/ — local file system cache
// Remote cache (Vercel, self-hosted) — shared across CI machines
// On cache HIT: Turborepo restores output files and replays logs. The task doesn't run.
// On cache MISS: Task runs, outputs are cached for next time.
// Force re-run (bypass cache):
turbo build --force
// See what would run without running:
turbo build --dry-run
// Profile task timing:
turbo build --profile=timing.json
npx @turbo/ui timing.json // visualise in browser
3. Remote Caching
Vercel remote cache, self-hosted cache server, and CI setup
# Remote cache: share cache hits across team members and CI
# Default: Vercel (free for personal/hobby, paid for teams):
npx turbo login
npx turbo link # link to Vercel team
# CI: set env vars, no interactive login needed:
# TURBO_TOKEN=your-vercel-token
# TURBO_TEAM=your-vercel-team-slug
# GitHub Actions with remote cache:
# - name: Build
# run: turbo build
# env:
# TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
# TURBO_TEAM: ${{ vars.TURBO_TEAM }}
# Self-hosted remote cache (no Vercel dependency):
# Option 1: ducktape (open source, drop-in replacement)
# Option 2: Turborepo remote cache server protocol (any S3-compatible store)
# docker-compose.yml for self-hosted ducktape cache:
# services:
# turbo-cache:
# image: fox1t/ducktape:latest
# environment:
# TURBO_TOKEN: "your-secret-token"
# volumes: ["./cache:/app/cache"]
# ports: ["3000:3000"]
# Use self-hosted cache:
turbo build --api="http://your-cache-server:3000" --token="your-secret-token" --team="team"
# Or in turbo.json:
# { "remoteCache": { "apiUrl": "http://your-cache-server:3000", "preflight": true } }
4. Filtering & Running Tasks
–filter to run tasks for specific packages, affected packages, and parallel execution
# Run tasks in all packages: turbo build # build everything turbo build --parallel # force parallel (ignores dependsOn ordering) # --filter: target specific packages: turbo build --filter=web # only the "web" app turbo build --filter=@acme/* # all packages in the @acme scope turbo build --filter=!ui # everything EXCEPT ui # Affected packages (based on git diff from main): turbo build --filter=[main] # packages changed since main branch turbo build --filter=[HEAD^] # packages changed since last commit turbo build --filter=[HEAD^]... # changed packages AND their dependents # Run multiple tasks: turbo lint test type-check # run all three tasks # Run task only in specific package (equivalent to package's own script): turbo run build --filter=web # Interactive mode — select packages to run: turbo build --filter=... # Watch mode (dev): turbo dev # all dev servers, restarts affected packages on change # Package-level filtering — run a task in a package + all its dependents: turbo build --filter=...ui # ui + everything that depends on ui
5. Package Structure & Sharing Code
Internal packages, TypeScript configs, shared UI library, and common pitfalls
// packages/ui/package.json — internal UI library:
{
"name": "@myapp/ui",
"version": "0.0.0",
"private": true,
"exports": {
".": "./src/index.ts" // just-in-time (JIT) compilation — no build step needed
},
"devDependencies": {
"typescript": "latest",
"@myapp/tsconfig": "*" // internal tsconfig package
}
}
// packages/tsconfig/package.json:
{
"name": "@myapp/tsconfig",
"version": "0.0.0",
"private": true,
"exports": {
"./react": "./react.json",
"./base": "./base.json"
}
}
// packages/tsconfig/base.json:
// { "compilerOptions": { "strict": true, "moduleResolution": "bundler" } }
// apps/web/tsconfig.json — extends shared config:
{
"extends": "@myapp/tsconfig/react",
"compilerOptions": { "outDir": "dist" }
}
// Import internal package:
import { Button } from "@myapp/ui"; // resolved directly to src/index.ts
// Common pitfalls:
// 1. "exports" must match the actual file path — wrong path = silent failure
// 2. JIT packages (just src/) skip the build step but need all consumers to compile them
// 3. Side effects: mark UI packages as "sideEffects": false for better tree-shaking
// 4. Missing peer deps: if ui uses React, declare React as peerDependency not devDependency
// 5. turbo build --filter=... is slow first run — normal, building the cache
Track Node.js toolchain releases at ReleaseRun. Related: TypeScript Reference | Next.js App Router Reference | GitHub Actions Reference
🔍 Free tool: npm Package Health Checker — check turbo and monorepo tooling packages for known CVEs and active maintenance.
Founded
2023 in London, UK
Contact
hello@releaserun.com