Turbopack Incremental Compilation
Turbopack replaces monolithic rebuild cycles with a Rust-native demand-driven computation engine: instead of re-traversing entry points on every file change, it models the project as a graph of memoized functions and recomputes only the nodes a change actually invalidates. By isolating changed nodes, computing minimal deltas, and persisting serialized results across sessions, it reaches sub-50ms hot module replacement (HMR) and high cache reuse on warm starts. This guide details the graph engine, delta propagation, and invalidation strategy needed to run Turbopack in real Next.js projects; for the broader engine comparison and pipeline orchestration, start from esbuild & Turbopack Workflows before tuning the knobs below.
Turbopack ships as the development server engine inside Next.js. Enable it with next dev --turbopack (the --turbopack flag is stable as of Next.js 15). Standalone Turbopack outside Next.js is not yet stable for production.
Prerequisites
This guide assumes Next.js 15.x, Node 18.18+ (Node 20 LTS recommended), and a project using the App Router. Verify your toolchain before configuring anything:
# Next.js 15.x / Node 20+
node --version # v20.x or newer
npx next --version # Next.js 15.x
cat package.json | grep '"next"'
Pin Next.js exactly in CI. Turbopack’s on-disk cache format is tied to the engine version baked into each Next.js release; a minor bump can invalidate every persisted node, so an unpinned dependency turns warm starts into silent cold starts.
Core Mechanics
Demand-driven graph tracking
Turbopack constructs a persistent computation graph during the initial parse phase. Each unit of work — parse, resolve, transform — is a memoized function whose output is cached against its inputs. Imports resolve at the AST level and map to immutable module identifiers. When a source file mutates, the engine marks the dirty function nodes and recomputes only the affected subgraph, walking outward until a node’s output is unchanged and propagation stops. This is the foundation of every incremental claim Turbopack makes; the same demand-driven model underpins the engine comparison in esbuild & Turbopack Workflows.
Partial module evaluation
Only modules with changed AST nodes or updated dependency hashes are re-evaluated. The Rust execution engine isolates evaluation contexts so unchanged modules retain their compiled output. In benchmarked App Router projects, this drops incremental latency from roughly 800ms (full rebuild) to under 35ms for a localized edit in a tree exceeding 5,000 modules.
State persistence across sessions
Graph state survives process restarts through serialized snapshots written under .next/cache/turbopack. The engine persists module boundaries, hash digests, and node results, so a warm start restores the graph instead of re-parsing the dependency tree. The detailed on-disk layout and CI restoration strategy live in Configuring Turbopack cache for Next.js projects.
Configuration & CLI Reference
Turbopack configuration lives under the top-level turbopack key in next.config.js as of Next.js 15.3 (the older experimental.turbo namespace is deprecated and emits a warning). The block below is complete and runnable.
// next.config.js — Next.js 15.3.x / Node 20+
/** @type {import('next').NextConfig} */
const nextConfig = {
turbopack: {
// Map non-standard extensions to webpack-compatible loaders.
rules: {
'*.svg': {
loaders: ['@svgr/webpack'],
as: '*.js',
},
},
// Override module resolution aliases (mirror your tsconfig paths).
resolveAlias: {
'@/components': './src/components',
'@/lib': './src/lib',
},
// Resolve order. Defaults: .tsx .ts .jsx .js .mjs .cjs .json
resolveExtensions: ['.tsx', '.ts', '.jsx', '.js', '.mjs', '.json'],
},
};
module.exports = nextConfig;
# Next.js 15.3.x — start, trace, and reset
next dev --turbopack # enable the dev engine
NEXT_TURBOPACK_TRACING=1 next dev --turbopack # verbose timing trace
rm -rf .next && next dev --turbopack # discard all cache, full rebuild
Step-by-Step Workflow
- Enable the engine. Switch your
devscript tonext dev --turbopackand start it once to populate the cache. - Confirm the cache materialized. Run
ls -lh .next/cache/turbopack— a populated directory is the prerequisite for any warm-start gain. - Measure a warm start. Stop the server, restart with
next dev --turbopack, and compare ready-time against the first run; a warm start should be markedly faster. - Edit a leaf component and watch the terminal for
Compiled in <N>ms. A single-file edit should recompile in well under 200ms. - Trace a slow rebuild. If a rebuild exceeds 200ms, run
NEXT_TURBOPACK_TRACING=1 next dev --turbopackand inspect which node dominates the recompute.
Verify the delta path directly: open the browser DevTools Network tab, filter to WebSocket frames, and confirm a single-file edit ships a payload under ~50KB rather than a full bundle.
Debugging & Failure Modes
Symlink and dynamic-import edge cases
Symlinked directories and dynamic import() with variable paths can make the watcher miss filesystem events, leaving stale results. Prefer static string literals or explicit glob patterns for dynamic imports. For symlinked packages in a monorepo, declare them as workspace dependencies in package.json so the resolver tracks them. Resolution failures from these same patterns are diagnosed in depth in Debugging Turbopack module resolution errors.
Non-deterministic loaders
Webpack-compatible loaders registered under turbopack.rules must produce identical output for identical input. A loader that embeds a timestamp or random id breaks the memoization contract and forces perpetual recompilation, defeating the cache.
Environment-variable drift
Environment variables are folded into node cache keys. Editing .env.local invalidates affected boundaries, and the running server may hold a stale value. Restart the dev server after any env change to guarantee a consistent graph.
Forced rebuild
When HMR stops reflecting changes or the cache appears corrupt:
# Most reliable reset
rm -rf .next && next dev --turbopack
# Bisect against the webpack bundler to isolate a Turbopack-specific bug
next dev
If a page reflects edits under next dev (webpack) but not under --turbopack, capture NEXT_TURBOPACK_TRACING=1 output and file it against Next.js with reproduction steps.
Performance & Measurement
| Metric | Cold start (no cache) | Warm start (restored cache) |
|---|---|---|
| Initial ready time | full graph build | restored snapshot, markedly faster |
| Single-file HMR | n/a | < 50ms typical |
| HMR payload | n/a | < 50KB per leaf edit |
| Recompiled nodes | entire reachable graph | invalidated subgraph only |
Measure ready time from the terminal banner, recompile latency from Compiled in <N>ms, and payload size from WebSocket frames in DevTools. Treat any single-file rebuild over 200ms as a module-boundary smell worth tracing.
Compatibility Matrix
| Next.js | Config key | Node | Notes |
|---|---|---|---|
| 13.x | experimental.turbo |
16.14+ | Alpha dev engine, frequent cache breakage |
| 14.x | experimental.turbo |
18.17+ | Dev engine stabilizing |
| 15.0–15.2 | experimental.turbo |
18.18+ | --turbopack flag stable |
| 15.3+ | turbopack (top-level) |
18.18+ | experimental.turbo deprecated, warns |
Related
- esbuild & Turbopack Workflows — the engine comparison and pipeline orchestration that frames this guide.
- Configuring Turbopack cache for Next.js projects — cache layout, lifecycle, and CI restoration.
- Debugging Turbopack module resolution errors — diagnosing “Module not found” under
--turbopack. - Custom Loaders and Asset Handling — loader registration and transformation constraints.