Configuring Turbopack Cache for Next.js Projects

Turbopack’s caching layer is a deterministic state machine that persists incremental compilation results to disk, and how you manage it directly dictates dev-server latency, memory footprint, and hot-reload fidelity. This guide covers the cache directory layout, the Next.js integration points you can actually control, lifecycle recovery, and CI restoration; for the graph engine that produces these cache entries in the first place, read Turbopack Incremental Compilation first.

Turbopack in Next.js is activated with next dev --turbopack (the --turbopack flag is stable as of Next.js 15). All cache artifacts are stored under .next/cache/turbopack by default.

Turbopack content-addressed cache lookup A source module is hashed with compiler flags and env digest; a matching key replays the cached output, a miss recompiles and writes a new entry. Content-addressed cache: key = hash(source + flags + env) Source module + flags + env Content hash key lookup Hit: replay cached output no transformation Miss: recompile write new entry .next/cache/turbopack persisted entries restored on warm start and across CI runs via actions/cache
Figure: each module is keyed by a content hash over source, compiler flags, and environment; a hit replays cached output while a miss recompiles and persists a new entry.

Prerequisites & Reproducible Setup

This walkthrough uses Next.js 15.x with Node 20. Reproduce a clean baseline:

# Next.js 15.x / Node 20+
npx create-next-app@latest tp-cache-demo --ts --app --no-tailwind
cd tp-cache-demo
npm pkg set scripts.dev="next dev --turbopack"
npm run dev    # populates .next/cache/turbopack on first start

Diagnosis Workflow

Work from cheapest signal to most destructive fix:

  1. Confirm the cache exists and is writable: ls -lh .next/cache/turbopack.
  2. Stop the server, restart, and compare ready time — a warm start that matches the cold start means the cache is not being reused.
  3. Capture a trace when starts are slow: NEXT_TURBOPACK_TRACING=1 next dev --turbopack 2>&1 | tee turbopack-trace.log.
  4. Only if hot reload stops reflecting edits or you see Error: Failed to read cache entry, clear the cache directory.

The cache directory path is not exposed as a user-configurable option in the stable API — Next.js manages it under .next/cache. To relocate the whole Next.js cache, set NEXT_CACHE_DIR, or point your CI cache at .next/cache.

Solution Configuration

The turbopack block controls loaders, aliases, and resolution order (the inputs that participate in cache keys); the cache lifecycle itself is driven by where you persist .next/cache. This next.config.js is complete and runnable on Next.js 15.3+.

// next.config.js — Next.js 15.3.x / Node 20+
/** @type {import('next').NextConfig} */
const nextConfig = {
  turbopack: {
    // Loaders feed into the cache key — keep them deterministic.
    rules: {
      '*.svg': { loaders: ['@svgr/webpack'], as: '*.js' },
    },
    resolveAlias: {
      '@/lib': './src/lib',
    },
    resolveExtensions: ['.tsx', '.ts', '.jsx', '.js', '.mjs', '.cjs', '.json'],
  },
};

module.exports = nextConfig;

Restore the cache across CI runs so fresh runners do not pay a full cold build:

# .github/workflows/build.yml — actions/cache v4
- name: Cache Next.js Turbopack artifacts
  uses: actions/cache@v4
  with:
    path: .next/cache
    key: ${{ runner.os }}-nextjs-turbopack-${{ hashFiles('**/package-lock.json', '**/yarn.lock') }}
    restore-keys: |
      ${{ runner.os }}-nextjs-turbopack-

Invalidate the local cache automatically when dependencies change so stale graphs never linger:

# .husky/pre-commit — clear cache when a manifest changes
#!/bin/sh
if git diff --cached --name-only | grep -qE 'package\.json|package-lock\.json|yarn\.lock|pnpm-lock\.yaml'; then
  echo "Dependency manifest changed. Clearing Turbopack cache..."
  rm -rf .next/cache/turbopack
fi

Verification

After wiring CI caching, confirm reuse with the action log: a restored run prints Cache restored from key: <os>-nextjs-turbopack-<hash> and the build’s ready time should fall well below the cold baseline. Locally, prove keying works by editing one component and watching ls .next/cache/turbopack — only new entries appear; the bulk of the directory is untouched. A trace comparison (NEXT_TURBOPACK_TRACING=1) between two identical-source starts should show near-zero recompiled nodes on the second run.

Gotchas & Edge Cases

Perpetual cache misses despite a restored cache. A cache key that hashes a frequently-changing lock file (a monorepo root package-lock.json bumped by unrelated packages) invalidates every run. Scope the key to the app’s own lock file: hashFiles('apps/my-app/package-lock.json') with a restore-keys fallback.

EACCES: permission denied on the cache directory. In Docker the .next directory is often created by a different user than the one running next dev. Fix ownership: RUN chown -R node:node /app/.next then USER node.

Ephemeral container storage. Default cache paths in containers may sit on ephemeral storage, so every start is cold. Mount a persistent volume at /app/.next/cache.

Non-deterministic output hashes on identical source. A Date.now() or timestamp inside next.config.js or a custom loader poisons the key. Audit configuration and loaders, then diff two traces under NEXT_TURBOPACK_TRACING=1 to find the drifting input.