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.
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:
- Confirm the cache exists and is writable:
ls -lh .next/cache/turbopack. - Stop the server, restart, and compare ready time — a warm start that matches the cold start means the cache is not being reused.
- Capture a trace when starts are slow:
NEXT_TURBOPACK_TRACING=1 next dev --turbopack 2>&1 | tee turbopack-trace.log. - 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.
Related
- Turbopack Incremental Compilation — the graph engine and invalidation model behind these cache entries.
- Debugging Turbopack module resolution errors — when
resolveAliasand cache state interact to produce “Module not found”. - esbuild & Turbopack Workflows — aligning cache retention across multiple bundlers.