Skip to main content

Turborepo Caching: Speed Up Monorepo Builds 10x

Turborepo caching is a core feature that makes incremental builds blazingly fast. When you run turbo build after changing a component, Turborepo detects that only one package changed, skips unchanged packages, and serves their build outputs from cache. In CI/CD, distributed caching (using Turbocloud or your own storage) eliminates redundant rebuilds across CI runs and developer machines. This article shows you how to configure caching, understand cache invalidation, and integrate distributed caching into your pipeline.

How Turborepo Caching Works

Turborepo computes a hash for each task based on inputs: the source code, configuration files, and environment variables. If the hash matches a cached result, Turborepo skips the task and restores the cached outputs. If inputs change, the hash changes and the task runs.

For example, when you run turbo build:

  1. Turborepo reads turbo.json and determines which packages have a build script.
  2. For each package, Turborepo hashes the source files listed under inputs in turbo.json.
  3. If a matching hash exists in cache, outputs are restored instantly.
  4. If no match, the task runs and outputs are cached for next time.

Configuring Task Outputs

Declare what each task produces by configuring the outputs field in turbo.json. Outputs are directories or files that should be cached:

{
"$schema": "https://turbo.build/schema.json",
"version": "2",
"tasks": {
"build": {
"outputs": ["dist/**", "build/**", ".next/**"],
"cache": true,
"inputs": ["src/**", "public/**", "package.json", "tsconfig.json"]
},
"test": {
"outputs": [".coverage/**"],
"cache": true,
"inputs": ["src/**", "__tests__/**", "jest.config.js"]
},
"dev": {
"cache": false,
"interactive": true,
"persistent": true
},
"lint": {
"outputs": [".eslint-cache/**"],
"cache": true,
"inputs": ["src/**", ".eslintrc.json"]
}
}
}

Each task specifies:

  • outputs: directories to cache after the task completes.
  • cache: whether to use caching (false for interactive tasks like dev).
  • inputs: files whose changes invalidate the cache. Omitting inputs caches based on all source files.

Understanding Cache Invalidation

Cache is invalidated when:

  • Any input file listed in inputs changes.
  • The task definition in turbo.json changes.
  • Environment variables referenced in the script change.

To understand what triggered a rebuild, run Turborepo with verbose output:

turbo build --verbose

This prints the hash and whether it was a cache hit or miss. Example output:

tasks/build:build [HASH=a1b2c3d4] CACHE MISS
tasks/build:build [HASH=a1b2c3d4] CACHE HIT

If you change src/Button.tsx in your component library, the hash changes, triggering a rebuild. Unchanged packages hit cache.

Handling Monorepo-Wide Configuration Changes

Some changes affect all packages, like updating TypeScript or ESLint. To reflect this in caching, add configuration files to the root-level task definition. In turbo.json:

{
"globalDependencies": [
"tsconfig.json",
".eslintrc.json",
".prettierrc"
],
"tasks": {
"build": {
"outputs": ["dist/**"],
"inputs": ["src/**", "tsconfig.json", "package.json"]
}
}
}

Now, if the root tsconfig.json changes, all package caches are invalidated.

Local Caching and Cache Location

By default, Turborepo caches to .turbo in the monorepo root. View the cache:

ls -la .turbo/cache

To clear the cache:

turbo prune --scope=web --docker
# or
rm -rf .turbo

Then re-run your task to repopulate cache:

turbo build

The first run is slow (all tasks execute), but subsequent runs with the same inputs are instant.

Distributed Caching with Turbocloud

For large teams, distributed caching stores task outputs in a remote service (Turbocloud, AWS S3, or your own server). All developers and CI machines share the cache, so CI does not re-run tasks already completed locally.

To enable Turbocloud (provided by Vercel):

  1. Create a free account at turbo.build/cloud.
  2. Link your repo:
turbo login
  1. Connect your repository. Turbocloud stores your outputs securely.

  2. Your team now shares cache:

turbo build # Uses Turbocloud automatically

When a developer builds locally, outputs are uploaded to Turbocloud. CI pulls those outputs instead of re-running. This can reduce CI build time by 50–80%.

Self-Hosted Distributed Caching

If you cannot use Turbocloud, use a self-hosted remote cache. Configure it in turbo.json:

{
"remoteCache": {
"apiUrl": "https://cache.mycompany.com",
"signature": true
}
}

Your cache server must implement the HTTP cache protocol (PUT for storing, GET for fetching). Turborepo sends artifacts to your server and retrieves them on cache hits.

Integrating Caching in CI/CD

In CI, pass environment variables to ensure cache keys match local development. Example GitHub Actions workflow:

name: Build and Test
on: [push, pull_request]

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v2
- uses: actions/setup-node@v3
with:
node-version: 18
cache: pnpm

- name: Install dependencies
run: pnpm install

- name: Build with Turborepo
run: turbo build

- name: Test
run: turbo test

This workflow installs dependencies and runs turbo build, which uses Turbocloud automatically if you ran turbo login.

Cache Debugging and Inspection

If a task unexpectedly misses cache, inspect the hash:

turbo build --verbose --log-order=grouped

This prints detailed logs showing hash computation. Look for:

  • Input files that changed.
  • Environment variables affecting the hash.
  • Configuration file changes.

To force a cache miss and rebuild everything:

turbo build --force

Excluding Files from Cache

By default, Turborepo caches task outputs but not node_modules. If a task creates files you do not want cached (logs, temp files), list them in .turborepoignore:

logs/
.env.local
temp/

Key Takeaways

  • Configure outputs in turbo.json to tell Turborepo what to cache after each task.
  • Specify inputs to control when cache is invalidated; omitting inputs uses all source files.
  • Add root-level configuration files to globalDependencies so monorepo-wide changes invalidate all caches.
  • Use Turbocloud for distributed caching across your team and CI, reducing build times by 50–80%.
  • Run turbo build --verbose to understand cache hits and misses during development.

Frequently Asked Questions

Does caching work across branches?

No. Each branch has separate source files, so hashes differ. However, Turbocloud caches by hash, so if you revert a file to match a previous commit, the cache hit applies across branches.

What if a task has random outputs (timestamps)?

Caching breaks with non-deterministic outputs. Ensure your build is deterministic: disable source maps with timestamps, pin versions, and use fixed seeds for any randomness. Then caching works reliably.

Can I cache test results?

Yes. Configure "test" with outputs: [".coverage/**"] to cache coverage reports. Next time, if tests did not change, results are restored instantly. However, consider running tests in CI always to ensure code quality.

How much disk space does the cache use?

.turbo can grow to hundreds of MB with large builds. Run turbo prune or rm -rf .turbo periodically. Turbocloud manages remote cache size automatically.

Do I need to commit .turbo to git?

No. .turbo is local to each machine. Add it to .gitignore. Distributed caching (Turbocloud) replaces the need for local cache in CI.

Further Reading