Tiếng Việt

Home

Turbopack: how much faster, measured on a real app

Since Next.js 16, Turbopack is the default bundler for both next dev and next build. The real question is not “is it faster” but “how much faster, on an app of what size, and at what cost”. This page builds a 52-page app on next@16.4.4 three cold times with each bundler, runs both production builds, then measures dev: process start to the first page, one route compiled on demand, and one file edit until the new text is served. No number below is written from memory.

The code on this page is an app that runs: 14/14 checks passed on 2026-11-10. Versions run: next@16.4.4, react@19.3.0, Node.js v26.0.0.

Which bundler is running

In Next.js 16.4.4, next build and next dev need no flag: the first line of the output reads ▲ Next.js 16.4.4 (Turbopack). To compare with webpack, add --webpack to the command and the line becomes ▲ Next.js 16.4.4 (webpack). The --turbo and --turbopack flags still appear in --help but are no longer needed — Turbopack is the default.

That is the change in 16: the release notes call Turbopack the “Default bundler for all apps”. For an app still on 15, it means your next build already changed bundler without anyone typing a command — so read the webpack key section below before you upgrade.

The sample app's next.config.ts says nothing about the bundler: the point of the page is to measure the default, not a pretty config. If you do need to configure Turbopack, since 16 it lives under the top-level turbopack key, no longer inside experimental.turbopack.

next.config.ts
import type { NextConfig } from "next";

// Nothing here: the point of the guide is the bundler Next.js picks on its own.
// `next build` and `next dev` use Turbopack; add --webpack to either to compare.
const nextConfig: NextConfig = {};

export default nextConfig;

The sample app, and how it was measured

The app has 52 pages: a home page, a blog index, and 50 posts generated by generateStaticParams. The home page prints 200 rows and has one Client Component using useState, so the browser bundle has real work too. No images, no third-party libraries — add those and the number describes the library, not the bundler.

The method: delete .next, run next build, time the whole process, three times per bundler, alternating (Turbopack, webpack, Turbopack, …) so one slow minute of the machine hits both sides equally. The number on this page is the median of three. The machine: Apple M4 Pro, 12 cores, Node.js 26.0.0, macOS 26.5.2, nothing else running during the measurement.

Your machine will give a different ratio — a large app, many libraries, many dynamic routes all move it. What travels is not the 2.9x, but the method and its two conditions: the same commit, and the same page content after the build.

app/page.tsx
import { Counter } from "./components/counter";

/** A server component with enough rows and a client child to give a bundler something to do. */
export default function Home() {
  const rows = Array.from({ length: 200 }, (_, i) => `Dòng ${i + 1}: next build, next dev`);
  return (
    <main>
      <h1>Bản đầu: Turbopack nhanh hơn bao nhiêu</h1>
      <p>
        Một ứng dụng nhỏ, build hai lần bằng hai bundler khác nhau. Mọi con số trên trang này do{" "}
        <code>scripts/guides/verify-turbopack.ts</code> đo.
      </p>
      <Counter />
      <ul>
        {rows.map((r) => (
          <li key={r}>{r}</li>
        ))}
      </ul>
    </main>
  );
}
app/blog/[slug]/page.tsx
import { notFound } from "next/navigation";
import { POSTS } from "../posts";

/** 50 prerendered pages: enough static work for a build time to mean something. */
export function generateStaticParams() {
  return POSTS.map((slug) => ({ slug }));
}

export default async function Post({ params }: { params: Promise<{ slug: string }> }) {
  const { slug } = await params;
  if (!POSTS.includes(slug)) notFound();
  return (
    <main>
      <h1>{`Bài ${slug.replace("bai-", "")}`}</h1>
      <p>Nội dung cố định, để hai bundler phải làm cùng một việc.</p>
    </main>
  );
}

Production build: 2.4 seconds against 7.0

Median of three cold builds: 2.4 s with Turbopack, 7.0 s with webpack — 2.9x. The individual runs: 3.2 / 2.4 / 2.3 s against 6.9 / 7.0 / 7.1 s. On this small app Turbopack stays between 2.3 and 3.2 s, and webpack between 6.9 and 7.1 s.

The output differs too: .next after the build is 28.9 MB with Turbopack and 65.8 MB with webpack. The same 50 pages are prerendered, and the two production servers return exactly the same text for / and /blog/bai-7 — Turbopack does not change what the page says, only how it is produced. That is the condition that makes the comparison mean anything: if one side built a different page, being faster would not be a win.

Do not read 2.9x as a promise for CI. On a small app most of the build is fixed cost (reading config, loading Next.js, type-checking); Turbopack cuts the compilation part, and that part grows with the app. If you want your number, measure your app.

Dev: where Turbopack earns its place

Three dev numbers, same app, one clean run per bundler: from spawning the next dev process to the home page answering 200 with content — 1,422 ms with Turbopack, 3,343 ms with webpack. The route /blog/bai-42 compiled on its first request: 426 ms against 507 ms. And the edit that matters most: changing the heading in app/page.tsx, measured until the new text came back — 119 ms against 187 ms.

The first number includes Next.js startup, so it is the real experience of opening a terminal, not pure compilation. The third is what you feel hundreds of times a day: both bundlers return the edited page in under 0.2 seconds. Both are fast; the difference is how often you wait.

One measurement caveat: Turbopack's dev file-system cache is still beta in 16, so every run here deletes .next — this is a cold start. Turn the cache on and the second run gets faster still, but that number says nothing about the first time a colleague opens the project.

Why it is faster

The Next.js docs describe Turbopack as a tool written in Rust, and that architecture explains both the gain and the limit: it does not run your JavaScript inside a Node process to compile, so places that must call a JavaScript function during the build — sassOptions.functions, for example — are documented as unsupported, with the advice to use webpack.

In dev, Turbopack compiles per route when a request arrives, instead of compiling the whole app before serving. That is why the first page is fast while an unseen route still costs one compile — exactly the 426 ms above. On an app with hundreds of routes this is the biggest difference from waiting for one enormous dev build.

You do not need the internals to use it. You need two things: check what Turbopack does not do before you upgrade, and treat any speed number as meaningful only on the same app at the same commit.

The upgrade trap: a `webpack` key in next.config

This is a build-stopping error, measured word for word: add a webpack(config) function to next.config.ts and run the default next build, and the build exits 1 with ⨯ ERROR: This build is using Turbopack, with a webpack config and no turbopack config. The same config with next build --webpack exits 0.

Which means an app with a webpack config will not build on Next.js 16 until you choose one of two paths: move the config to turbopack (the docs cover rules for loaders, resolveAlias, resolveExtensions), or add --webpack to the build command and accept the slower build. Do not let this error appear for the first time on a release CI.

Before upgrading, grep next.config for webpack, and check whether the loaders and plugins you use have Turbopack equivalents. The Turbopack docs list what is not supported yet — reading that section is far cheaper than an afternoon of fixing CI.

When to turn Turbopack off

Three legitimate cases: a webpack config you have not moved yet, a loader or plugin that only exists for webpack, and a specific Turbopack bug blocking you (then file an issue with a reproduction — do not just switch off). All three use --webpack: no fork, no downgrade.

The measured cost: builds about 2.9x slower and .next about 2.3x larger. For one CI build a week that is nothing; for dev it matters more, because next dev --webpack is slower on all three numbers above.

What not to do: turn Turbopack off “to be safe”. If the app has no webpack config and the build is green, the default is the lower-maintenance path — it is the path Next.js will test hardest in later releases.

Measure it on your own app

1. Pin one commit and change nothing between runs. 2. Delete .next before each build, or you are measuring cache. 3. Three runs per bundler, alternating, take the median — one run on a machine also running Docker will lie. 4. Compare page content after the build, not only time: curl one static page and one page with a parameter on both production servers. 5. Record the .next size; it affects deploy time and disk on a VPS. 6. For dev, measure three things: first open, an unseen route, and one file edit — the third is the number you live with.

This page's script does exactly those six things and prints each step. It does not assert that Turbopack is faster; it records what it measured, and fails if the two bundlers built different apps. That is also how to read any benchmark, including this one.

Checked

  • both bundlers build the app (next 16.4.4, 52 pages: home, blog index, 50 posts): exit 0, exit 0
  • what a plain `next build` uses in 16 (no flag): ▲ Next.js 16.4.4 (Turbopack)
  • what `next build --webpack` uses: ▲ Next.js 16.4.4 (webpack)
  • cold production build, median of 3, same app: 2.4 s Turbopack vs 7.0 s webpack (2.9x); runs 3.2/2.4/2.3 vs 6.9/7.0/7.1
  • .next on disk after the build: 28.9 MB Turbopack vs 65.8 MB webpack
  • prerendered pages from generateStaticParams: 50 Turbopack, 50 webpack
  • the two production builds serve the same text: 200/200 on /, 200/200 on the post; same text
  • what a plain `next dev` uses: ▲ Next.js 16.4.4 (Turbopack)
  • what `next dev --webpack` uses: ▲ Next.js 16.4.4 (webpack)
  • dev: process start until the first page is on screen: 1422 ms Turbopack vs 3343 ms webpack
  • dev: a route compiled on first request (/blog/bai-42): 426 ms Turbopack vs 507 ms webpack
  • dev: edit app/page.tsx until the new text is served: 119 ms Turbopack vs 187 ms webpack
  • a `webpack` key in next.config, built by Turbopack: exit 1: ⨯ ERROR: This build is using Turbopack, with a `webpack` config and no `turbopack` config.
  • the same config, built by `--webpack`: exit 0

Measuring script (not public yet): bun scripts/guides/verify-turbopack.ts

Related

Sources

Our summary and wording; the code is rewritten from the examples in the Next.js docs. Where they differ, the Next.js docs win.