Turbopack and Build Performance
The idea in one sentence
Section titled “The idea in one sentence”Turbopack is Next.js’s Rust-based bundler, now the default for both next dev and next build, and it speeds up your feedback loop by compiling incrementally instead of rebuilding everything.
Turbopack is now the default
Section titled “Turbopack is now the default”In current Next.js (16 at the time of writing), Turbopack is the default bundler for both next dev and next build. Development with Turbopack has been stable for some time; the production build path is the newer piece, so verify against the docs for your exact version. You do not need any flag to use it — a plain script is enough.
{ "scripts": { "dev": "next dev", "build": "next build", "start": "next start" }}If you hit an issue and need the old bundler, you can opt back into webpack per command.
{ "scripts": { "dev": "next dev", "build": "next build --webpack", "start": "next start" }}A next.config that has a custom webpack() function and no turbopack key makes the default next build fail rather than silently ignore it — pass --webpack to keep webpack, or --turbopack to ignore the webpack config.
What Turbopack speeds up
Section titled “What Turbopack speeds up”The core idea is incremental compilation. Turbopack builds a dependency graph and only recomputes the parts affected by a change, so cold starts are faster and Hot Module Replacement stays near-instant even as the app grows. When you save a file, it recompiles just that module and its dependents rather than the whole route.
Migrating a webpack loader: resolveAlias and rules
Section titled “Migrating a webpack loader: resolveAlias and rules”webpack is a mature JavaScript bundler with a huge plugin ecosystem, but that JavaScript core and its full-rebuild tendencies become the bottleneck on large apps. Turbopack is written in Rust, is multi-core by design, and treats fine-grained incremental work as the default rather than an add-on. Turbopack does not support webpack plugins at all — only a tested subset of webpack loaders — and you configure it under the top-level turbopack key (the old name was experimental.turbo, still an alias, but new configs should use the top-level key).
Two options carry over almost every webpack resolve config people actually use:
resolveAlias— the direct equivalent of webpack’sresolve.alias, for mapping an import specifier to a different module.rules— the equivalent of webpack’smodule.rules, for applying a loader (e.g.@svgr/webpack,yaml-loader,raw-loader) to files matching a glob.
// next.config.ts — Turbopack-specific configurationimport type { NextConfig } from 'next'
const nextConfig: NextConfig = { turbopack: { resolveAlias: { underscore: 'lodash', // same shape as webpack's resolve.alias }, resolveExtensions: ['.mdx', '.tsx', '.ts', '.jsx', '.js', '.json'], // rules: { '*.svg': { loaders: ['@svgr/webpack'], as: '*.js' } } },}
export default nextConfigThe gap to watch for during migration: Turbopack’s loader runner (loader-runner) implements most of the standard loader API, but not all of it — this.importModule/this.loadModule have no support, this.emitFile has no support, and this.fs only implements readFile. A loader that leans on those APIs needs a Turbopack-compatible alternative, not just a rules entry.
The React Compiler: real cost, and whether you need the Babel plugin
Section titled “The React Compiler: real cost, and whether you need the Babel plugin”reactCompiler: true turns on automatic memoization, removing a lot of manual useMemo/useCallback. The catch the docs are explicit about: the compiler runs through a Babel plugin — Next.js applies a custom SWC pass first to limit which files even get sent through Babel, but you still pay a real, if localized, build-time cost, and you must install babel-plugin-react-compiler yourself.
import type { NextConfig } from 'next'
const nextConfig: NextConfig = { reactCompiler: true, // requires: npm install -D babel-plugin-react-compiler}
export default nextConfigNext.js 16.3 added an escape hatch from that Babel cost: experimental.turbopackRustReactCompiler runs a native Rust port of the compiler directly inside Turbopack — no Node.js Babel pass, and no need to install babel-plugin-react-compiler at all. It still requires reactCompiler: true to be set (it only selects which implementation runs), and it only works under Turbopack.
// next.config.ts — 16.3+: skip the Babel plugin entirelyimport type { NextConfig } from 'next'
const nextConfig: NextConfig = { reactCompiler: true, experimental: { turbopackRustReactCompiler: true, // native Rust port, Turbopack-only, no babel-plugin-react-compiler needed },}
export default nextConfigDebugging a build: —debug-prerender and —profile
Section titled “Debugging a build: —debug-prerender and —profile”Two next build flags, both real and present in this exact Next.js version (npx next build --help):
--debug-prerender— “Enables debug mode for prerendering. Not for production use!” — for tracing exactly what happens while a route is being prerendered, when the static/dynamic split isn’t behaving as expected.--profile— “Enables production profiling for React,” for capturing a React profiler trace of a production build.
next build --debug-prerender # trace prerendering decisions, dev-only diagnosticnext build --profile # React production profilingnext build --experimental-analyze also exists as a build-time flag equivalent to the standalone next experimental-analyze command covered in the previous lesson.
Build caching
Section titled “Build caching”Turbopack can persist its work to disk so a later dev start or build reuses earlier compilation instead of starting cold. Both caches are on by default (dev since 16.1, build since 16.3) even though the options still live under experimental; the build cache lives in .next/cache/turbopack, so it only helps when your CI restores .next/cache between builds — set the option to false to opt out.
// next.config.ts — both caches are on by default; opt out per commandimport type { NextConfig } from 'next'
const nextConfig: NextConfig = { experimental: { turbopackFileSystemCacheForDev: true, // default: true (16.1+) turbopackFileSystemCacheForBuild: false, // default: true (16.3+); false if CI never restores .next/cache },}
export default nextConfigflowchart LR Save[Save a file] --> Graph[Dependency graph] Graph --> Changed[Recompile changed module + dependents] Graph --> Cache[Reuse unchanged from cache] Changed --> HMR[Fast HMR / build] Cache --> HMR Compiler["reactCompiler: true"] -->|Babel plugin, default| Slow[Localized build cost] Compiler -->|turbopackRustReactCompiler: true, 16.3+| Fast[Native Rust, no babel-plugin-react-compiler]
Mental Model
Section titled “Mental Model”Turbopack’s rules/resolveAlias are a translation layer, not a reimplementation — you’re telling Turbopack “here’s the webpack loader you already trust, run it the way you always have,” which is why the loader API surface (not the bundler’s core behavior) is where migrations actually break. The React Compiler split (Babel vs. the Rust port under turbopackRustReactCompiler) is the same shape of choice as Turbopack itself vs. webpack: one path is mature and works everywhere with an extra dependency, the newer path drops that dependency but is scoped to Turbopack only.
Trade-offs
Section titled “Trade-offs”| What you get | Benefit | Cost |
|---|---|---|
Turbopack default (next dev/next build) | Faster cold starts, near-instant HMR | Webpack plugins don’t work at all; only a tested loader subset does |
turbopack.resolveAlias/rules | Direct webpack resolve.alias/loader equivalents | Loaders using importModule, emitFile, or most of this.fs need a different approach |
reactCompiler: true (Babel, default path) | Automatic memoization, less manual useMemo/useCallback | Real Babel-plugin build cost; requires installing babel-plugin-react-compiler |
+ turbopackRustReactCompiler: true (16.3+) | No Babel pass, no extra dependency to install | Experimental, Turbopack-only |
--debug-prerender / --profile | Real diagnostics for prerendering and React profiling | --debug-prerender is explicitly “not for production use” |
| Turbopack FS cache (on by default) | Faster repeat dev/build runs | Only helps in CI if .next/cache is actually restored between runs |
Common Misconceptions
Section titled “Common Misconceptions”- “Turbopack supports webpack plugins, just like it supports loaders.” It doesn’t — plugins are unsupported entirely; only a tested subset of loaders works, through
turbopack.rules. - “
reactCompiler: trueis all you need — Next.js runs it natively.” By default it still runs through the Babel plugin, which you must install yourself (babel-plugin-react-compiler); onlyturbopackRustReactCompiler: true(16.3+, Turbopack-only) skips that dependency. - “
--debug-prerenderis a safe flag to leave on for troubleshooting production builds.” The CLI’s own help text calls it out: “Not for production use!” - “The Turbopack filesystem cache speeds up every CI build automatically.” It only helps if your CI actually restores
.next/cachebetween runs — otherwise every build still starts cold regardless of the flag’s default-on status.
Reading AI-generated code
Section titled “Reading AI-generated code”When a generated snippet touches Turbopack or build configuration, check for these before trusting it:
- Does it configure a webpack plugin under
turbopack.rules? Only loaders are supported there — plugins have no Turbopack equivalent via this key. - Does it enable
reactCompiler: truewithout either installingbabel-plugin-react-compileror settingturbopackRustReactCompiler: true? One of the two is required. - Does it suggest
--debug-prerenderfor a production deployment rather than local debugging? - Does a custom
webpack()function coexist with noturbopackkey, assuming the defaultnext buildwill just ignore it? It fails the build instead. - Does it assume the build cache helps in CI without the pipeline actually restoring
.next/cachebetween runs?
🐛 Spot the bug
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {reactCompiler: true,}
export default nextConfigShow the answer
This turns on the React Compiler, but by default it runs through a Babel plugin that is not bundled with Next.js — you must separately npm install -D babel-plugin-react-compiler, or the build fails looking for that package. The two fixes: install the Babel plugin, or (Next.js 16.3+, Turbopack only) add experimental: { turbopackRustReactCompiler: true } next to reactCompiler: true to use the native Rust port instead, which needs no extra dependency at all.