9.5 KiB
Turborepo Caching Strategies
Local caching, remote caching, cache invalidation, and optimization techniques.
Local Caching
How It Works
Turborepo caches task outputs based on inputs:
- Hash inputs: Source files, dependencies, environment variables, config
- Run task: If hash not in cache
- Save outputs: Store in
.turbo/cache - Restore on match: Instant completion on cache hit
Default cache location: ./node_modules/.cache/turbo
Cache Configuration
// turbo.json
{
"pipeline": {
"build": {
"outputs": ["dist/**", ".next/**", "!.next/cache/**"],
"cache": true // default
},
"dev": {
"cache": false // don't cache dev servers
}
}
}
Outputs Configuration
Specify what gets cached:
{
"build": {
"outputs": [
"dist/**", // All files in dist
"build/**", // Build directory
".next/**", // Next.js output
"!.next/cache/**", // Exclude Next.js cache
"storybook-static/**", // Storybook build
"*.tsbuildinfo" // TypeScript build info
]
}
}
Best practices:
- Include all build artifacts
- Exclude nested caches
- Include type definitions
- Include generated files
Clear Local Cache
# Remove cache directory
rm -rf ./node_modules/.cache/turbo
# Or use turbo command with --force
turbo run build --force
# Clear and rebuild
turbo run clean && turbo run build
Remote Caching
Share cache across team and CI/CD.
Vercel Remote Cache (Recommended)
Setup:
# Login to Vercel
turbo login
# Link repository
turbo link
Use in CI:
# .github/workflows/ci.yml
env:
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
steps:
- run: turbo run build test
Get tokens from Vercel dashboard:
- Go to https://vercel.com/account/tokens
- Create new token
- Add as GitHub secrets
Custom Remote Cache
Configure custom remote cache server:
// .turbo/config.json
{
"teamid": "team_xxx",
"apiurl": "https://cache.example.com",
"token": "your-token"
}
Or use environment variables:
export TURBO_API="https://cache.example.com"
export TURBO_TOKEN="your-token"
export TURBO_TEAM="team_xxx"
Remote Cache Verification
# Check cache status
turbo run build --output-logs=hash-only
# Output shows:
# • web:build: cache hit, replaying logs [hash]
# • api:build: cache miss, executing [hash]
Cache Signatures
Cache invalidated when these change:
1. Source Files
All tracked Git files in package:
packages/ui/
├── src/
│ ├── button.tsx # Tracked
│ └── input.tsx # Tracked
├── dist/ # Ignored (in .gitignore)
└── node_modules/ # Ignored
2. Package Dependencies
Changes in package.json:
{
"dependencies": {
"react": "18.2.0" // Version change invalidates cache
}
}
3. Environment Variables
Configured in pipeline:
{
"build": {
"env": ["NODE_ENV", "API_URL"] // Changes invalidate cache
}
}
4. Global Dependencies
Files affecting all packages:
{
"globalDependencies": [
"**/.env.*local",
"tsconfig.json",
".eslintrc.js"
]
}
5. Task Configuration
Changes to turbo.json pipeline:
{
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**"] // Config changes invalidate cache
}
}
Input Control
Override Input Detection
Explicitly define what affects cache:
{
"build": {
"inputs": [
"src/**/*.ts", // Include TS files
"src/**/*.tsx", // Include TSX files
"!src/**/*.test.ts", // Exclude tests
"!src/**/*.stories.tsx", // Exclude stories
"package.json", // Include package.json
"tsconfig.json" // Include config
]
}
}
Use cases:
- Exclude test files from build cache
- Exclude documentation from production builds
- Include only source files, not generated files
Global vs Package Inputs
Global inputs (affect all packages):
{
"globalDependencies": [".env", "tsconfig.json"]
}
Package inputs (affect specific tasks):
{
"pipeline": {
"build": {
"inputs": ["src/**"]
}
}
}
Environment Variables
Cached Environment Variables
Include in cache signature:
{
"pipeline": {
"build": {
"env": [
"NODE_ENV", // Must match for cache hit
"NEXT_PUBLIC_API_URL",
"DATABASE_URL"
]
}
}
}
Cache invalidated when values change.
Pass-Through Environment Variables
Don't affect cache:
{
"pipeline": {
"build": {
"passThroughEnv": [
"DEBUG", // Different values use same cache
"LOG_LEVEL",
"VERBOSE"
]
}
}
}
Use for: Debug flags, log levels, non-production settings
Global Environment Variables
Available to all tasks:
{
"globalEnv": [
"NODE_ENV",
"CI",
"VERCEL"
]
}
Cache Optimization Strategies
1. Granular Outputs
Define precise outputs to minimize cache size:
// ❌ Bad - caches too much
{
"build": {
"outputs": ["**"]
}
}
// ✅ Good - specific outputs
{
"build": {
"outputs": ["dist/**", "!dist/**/*.map"]
}
}
2. Exclude Unnecessary Files
{
"build": {
"outputs": [
".next/**",
"!.next/cache/**", // Exclude Next.js cache
"!.next/server/**/*.js.map", // Exclude source maps
"!.next/static/**/*.map"
]
}
}
3. Separate Cacheable Tasks
{
"pipeline": {
"build": {
"dependsOn": ["^build"],
"cache": true
},
"test": {
"dependsOn": ["build"],
"cache": true // Separate from build
},
"dev": {
"cache": false // Never cache
}
}
}
4. Use Input Filters
Only track relevant files:
{
"build": {
"inputs": [
"src/**/*.{ts,tsx}",
"!src/**/*.{test,spec}.{ts,tsx}",
"public/**",
"package.json"
]
}
}
Cache Analysis
Inspect Cache Hits/Misses
# Dry run with JSON output
turbo run build --dry-run=json | jq '.tasks[] | {package: .package, task: .task, cache: .cache}'
View Task Graph
# Generate task graph
turbo run build --graph
# Output: graph.html (open in browser)
Cache Statistics
# Run with summary
turbo run build --summarize
# Output: .turbo/runs/[hash].json
CI/CD Cache Configuration
GitHub Actions
name: CI
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18
- name: Install dependencies
run: npm install
- name: Build and test
run: turbo run build test lint
env:
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
# Optional: Cache node_modules
- uses: actions/cache@v3
with:
path: node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
GitLab CI
image: node:18
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- node_modules/
- .turbo/
build:
stage: build
script:
- npm install
- turbo run build test
variables:
TURBO_TOKEN: $TURBO_TOKEN
TURBO_TEAM: $TURBO_TEAM
Troubleshooting
Cache Not Working
Check outputs are defined:
turbo run build --dry-run=json | jq '.tasks[] | {task: .task, outputs: .outputs}'
Verify cache location:
ls -la ./node_modules/.cache/turbo
Check environment variables:
echo $TURBO_TOKEN
echo $TURBO_TEAM
Cache Too Large
Analyze cache size:
du -sh ./node_modules/.cache/turbo
Reduce outputs:
{
"build": {
"outputs": [
"dist/**",
"!dist/**/*.map", // Exclude source maps
"!dist/**/*.test.js" // Exclude test files
]
}
}
Clear old cache:
# Turborepo doesn't auto-clean, manually remove:
rm -rf ./node_modules/.cache/turbo
Remote Cache Connection Issues
Test connection:
curl -I https://cache.example.com
Verify token:
turbo link
# Should show: "Remote caching enabled"
Check logs:
turbo run build --output-logs=full
Best Practices
- Define precise outputs - Only cache necessary files
- Exclude nested caches - Don't cache caches (.next/cache)
- Use remote caching - Share cache across team and CI
- Track relevant inputs - Use
inputsto filter files - Separate env vars - Use
passThroughEnvfor debug flags - Cache test results - Include coverage in outputs
- Don't cache dev servers - Set
cache: falsefor dev tasks - Use global dependencies - Share config across packages
- Monitor cache performance - Use
--summarizeto analyze - Clear cache periodically - Remove stale cache manually
Cache Performance Tips
For CI/CD:
- Enable remote caching
- Run only changed packages:
--filter='...[origin/main]' - Use
--continueto see all errors - Cache node_modules separately
For Local Development:
- Keep local cache enabled
- Don't force rebuild unless needed
- Use filters to build only what changed
- Clear cache if issues arise
For Large Monorepos:
- Use granular outputs
- Implement input filters
- Monitor cache size regularly
- Consider cache size limits on remote cache