Skip to content

Turbopack and Build Performance

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.

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.

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’s resolve.alias, for mapping an import specifier to a different module.
  • rules — the equivalent of webpack’s module.rules, for applying a loader (e.g. @svgr/webpack, yaml-loader, raw-loader) to files matching a glob.
// next.config.ts — Turbopack-specific configuration
import 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 nextConfig

The 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.

next.config.ts
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
reactCompiler: true, // requires: npm install -D babel-plugin-react-compiler
}
export default nextConfig

Next.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 entirely
import 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 nextConfig

Debugging 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.
Terminal window
next build --debug-prerender # trace prerendering decisions, dev-only diagnostic
next build --profile # React production profiling

next build --experimental-analyze also exists as a build-time flag equivalent to the standalone next experimental-analyze command covered in the previous lesson.

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 command
import 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 nextConfig
flowchart 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]
Incremental compilation reuses unchanged work

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.

What you getBenefitCost
Turbopack default (next dev/next build)Faster cold starts, near-instant HMRWebpack plugins don’t work at all; only a tested loader subset does
turbopack.resolveAlias/rulesDirect webpack resolve.alias/loader equivalentsLoaders using importModule, emitFile, or most of this.fs need a different approach
reactCompiler: true (Babel, default path)Automatic memoization, less manual useMemo/useCallbackReal Babel-plugin build cost; requires installing babel-plugin-react-compiler
+ turbopackRustReactCompiler: true (16.3+)No Babel pass, no extra dependency to installExperimental, Turbopack-only
--debug-prerender / --profileReal diagnostics for prerendering and React profiling--debug-prerender is explicitly “not for production use”
Turbopack FS cache (on by default)Faster repeat dev/build runsOnly helps in CI if .next/cache is actually restored between runs
  • “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: true is 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); only turbopackRustReactCompiler: true (16.3+, Turbopack-only) skips that dependency.
  • “--debug-prerender is 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/cache between runs — otherwise every build still starts cold regardless of the flag’s default-on status.

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: true without either installing babel-plugin-react-compiler or setting turbopackRustReactCompiler: true? One of the two is required.
  • Does it suggest --debug-prerender for a production deployment rather than local debugging?
  • Does a custom webpack() function coexist with no turbopack key, assuming the default next build will just ignore it? It fails the build instead.
  • Does it assume the build cache helps in CI without the pipeline actually restoring .next/cache between runs?

🐛 Spot the bug

next.config.ts
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
reactCompiler: true,
}
export default nextConfig
Show 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.

What language is Turbopack written in?
Does Turbopack support webpack plugins the same way it supports a tested subset of webpack loaders?
By default, what does reactCompiler: true require you to install, and what changed in 16.3?
What does the next build --help output say about --debug-prerender?
What is the Turbopack-native equivalent of webpack's resolve.alias?
A CI pipeline runs next build fresh every time and never restores .next/cache between runs. What does turbopackFileSystemCacheForBuild being true by default actually get them?