Turbopack: nhanh hơn bao nhiêu, đo trên một ứng dụng thật
Từ Next.js 16, Turbopack là bundler mặc định cho cả next dev và next build. Câu hỏi thật của người đọc không phải “nó có nhanh hơn không” mà “nhanh hơn bao nhiêu, trên ứng dụng cỡ nào, và cái giá là gì”. Trang này dựng một ứng dụng 52 trang trên next@16.4.4, build ba lần lạnh bằng mỗi bundler, chạy cả hai bản production, rồi đo dev: từ lúc khởi động tới trang đầu tiên, một route biên dịch theo yêu cầu, và một lần sửa tệp tới lúc chữ mới hiện ra. Không con số nào dưới đây viết từ ký ức.
Mã trên trang này là một ứng dụng chạy được: 14/14 kiểm tra đạt ngày 10/11/2026. Phiên bản đã chạy thử: next@16.4.4, react@19.3.0, Node.js v26.0.0.
Bundler nào đang chạy
Trong Next.js 16.4.4, next build và next dev không cần cờ nào: dòng đầu của output ghi ▲ Next.js 16.4.4 (Turbopack). Muốn so với webpack, thêm --webpack vào lệnh; output đổi thành ▲ Next.js 16.4.4 (webpack). Hai cờ --turbo và --turbopack vẫn còn trong --help nhưng không cần nữa — Turbopack đã là mặc định.
Đây là thay đổi của bản 16: thông báo phát hành gọi Turbopack là “Default bundler for all apps”. Với ứng dụng đang chạy 15, điều đó nghĩa là bản build tiếp theo của bạn đã đổi bundler mà không ai gõ lệnh gì — nên đọc mục khoá webpack bên dưới trước khi nâng cấp.
next.config.ts của ứng dụng mẫu để trống phần bundler: mục đích của trang là đo cái mặc định, không phải một cấu hình đẹp. Nếu bạn cần khai gì đó cho Turbopack, từ bản 16 nó nằm ở khoá cấp cao nhất turbopack, không còn trong experimental.turbopack.
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;Ứng dụng mẫu và cách đo
Ứng dụng có 52 trang: một trang chủ, một mục lục blog, và 50 bài sinh ra bằng generateStaticParams. Trang chủ in 200 dòng và có một Client Component dùng useState, để phần bundle trình duyệt cũng có việc thật. Không có ảnh, không có thư viện ngoài — nếu thêm vào, con số sẽ nói về thư viện đó chứ không nói về bundler.
Cách đo: xoá .next, chạy next build, đo cả tiến trình, lặp ba lần cho mỗi bundler và xen kẽ nhau (Turbopack, webpack, Turbopack, …) để một phút chậm của máy ảnh hưởng cả hai bên như nhau. Con số in trên trang là trung vị của ba lần. Máy đo: Apple M4 Pro, 12 nhân, Node.js 26.0.0, macOS 26.5.2, không chạy gì khác trong lúc đo.
Máy của bạn sẽ ra tỉ lệ khác — ứng dụng lớn, nhiều thư viện, nhiều route động thì tỉ lệ đổi. Phần mang về được không phải con số 2,9 lần, mà là cách đo và hai điều kiện đi kèm: cùng một commit, và cùng một nội dung trang sau khi build.
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>
);
}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>
);
}Build production: 2,4 giây so với 7,0 giây
Trung vị ba lần build lạnh: 2,4 giây với Turbopack, 7,0 giây với webpack — 2,9 lần. Ba lần riêng lẻ: 3,2 / 2,4 / 2,3 giây so với 6,9 / 7,0 / 7,1 giây. Trên ứng dụng nhỏ này, Turbopack nằm trong khoảng 2,3–3,2 giây và webpack trong khoảng 6,9–7,1 giây.
Đầu ra cũng khác: .next sau khi build là 28,9 MB với Turbopack và 65,8 MB với webpack. Cùng 50 trang được prerender, và hai bản production trả về đúng cùng một đoạn chữ cho / và /blog/bai-7 — Turbopack không đổi nội dung trang, chỉ đổi cách tạo ra nó. Đó là điều kiện để phép so sánh có nghĩa: nếu một bên build ra trang khác, nhanh hơn không còn là điểm cộng.
Đừng đọc 2,9 lần như một lời hứa cho CI. Trên ứng dụng nhỏ, phần lớn thời gian build là chi phí cố định (đọc cấu hình, nạp Next.js, type-check); Turbopack cắt phần biên dịch, mà phần đó càng lớn thì tỉ lệ càng có lợi cho nó. Muốn biết con số của bạn thì phải đo trên ứng dụng của bạn.
Dev: chỗ Turbopack đáng giá nhất
Ba con số dev, đo trên cùng ứng dụng, mỗi bundler một lần chạy sạch: từ lúc spawn tiến trình next dev tới lúc trang chủ trả 200 kèm nội dung — 1.422 ms với Turbopack, 3.343 ms với webpack. Route /blog/bai-42 biên dịch ở request đầu tiên: 426 ms so với 507 ms. Và lần sửa quan trọng nhất: đổi tiêu đề trong app/page.tsx, đo tới lúc chữ mới được trả về — 119 ms so với 187 ms.
Con số đầu tiên gồm cả khởi động Next.js, nên nó là trải nghiệm thật khi mở terminal, không phải thuần biên dịch. Con số thứ ba mới là thứ bạn cảm nhận hàng trăm lần một ngày: cả hai bundler đều trả trang đã sửa trong dưới 0,2 giây. Cả hai đều nhanh; khác biệt là ở tần suất bạn phải chờ.
Một lưu ý về cách đo: cache đĩa cho dev của Turbopack còn ở nhãn beta trong bản 16, nên ở đây mỗi lần chạy đều xoá .next — đó là khởi động lạnh. Nếu bật cache, lần chạy thứ hai sẽ nhanh hơn nữa, nhưng con số đó không nói gì về lần đầu tiên một đồng nghiệp mở dự án.
Vì sao nhanh hơn
Tài liệu Next.js mô tả Turbopack là công cụ viết bằng Rust, và kiến trúc đó giải thích cả ưu điểm lẫn giới hạn: nó không chạy JavaScript của bạn trong tiến trình Node để biên dịch, nên những chỗ cần gọi hàm JavaScript ngay trong lúc build — ví dụ sassOptions.functions — thì tài liệu nói thẳng là không hỗ trợ và khuyên dùng webpack.
Ở chế độ dev, Turbopack biên dịch theo route khi có request, thay vì biên dịch cả ứng dụng trước khi phục vụ. Vì vậy trang đầu tiên nhanh, còn route lạ vẫn tốn thêm một lần biên dịch — đúng như con số 426 ms ở trên. Với ứng dụng vài trăm route, đây là khác biệt lớn nhất so với việc chờ một lần build dev khổng lồ.
Không cần hiểu cơ chế bên trong để dùng nó. Cần hiểu hai điều: thứ gì Turbopack không làm được thì phải kiểm tra trước khi nâng cấp, và mọi con số tốc độ chỉ có nghĩa khi so trên cùng một ứng dụng, cùng một commit.
Chỗ vấp khi nâng cấp: khoá `webpack` trong next.config
Đây là lỗi chặn build, đo được nguyên văn: thêm một hàm webpack(config) vào next.config.ts rồi chạy next build mặc định, build thoát với mã 1 và dòng ⨯ ERROR: This build is using Turbopack, with a webpack config and no turbopack config. Cùng cấu hình đó, chạy next build --webpack, thoát 0.
Nghĩa là một ứng dụng đang có cấu hình webpack sẽ không build được trên Next.js 16 cho tới khi bạn chọn một trong hai: chuyển cấu hình sang turbopack (tài liệu có mục rules cho loader, resolveAlias, resolveExtensions), hoặc thêm --webpack vào lệnh build và chấp nhận chậm hơn. Đừng để lỗi này xuất hiện lần đầu trên CI của một bản phát hành.
Trước khi nâng cấp, hãy grep next.config xem có webpack không, và xem danh sách loader/plugin đang dùng có tương đương bên Turbopack không. Tài liệu Turbopack liệt kê rõ phần nào chưa hỗ trợ — đọc mục đó rẻ hơn nhiều so với một buổi chiều sửa CI.
Khi nào nên tắt Turbopack
Có ba trường hợp chính đáng: cấu hình webpack mà bạn chưa chuyển kịp, một loader hoặc plugin chỉ có bản webpack, và một lỗi Turbopack cụ thể đang chặn bạn (khi đó mở issue kèm bản tái hiện, đừng chỉ tắt). Cả ba đều dùng --webpack — không cần fork, không cần hạ phiên bản Next.js.
Cái giá đã đo: build chậm khoảng 2,9 lần và .next lớn khoảng 2,3 lần. Với một lần build CI mỗi tuần thì không đáng kể; với dev thì đáng kể hơn, vì next dev --webpack cũng chậm hơn ở cả ba con số trên.
Điều không nên làm: tắt Turbopack vì “cho chắc”. Nếu ứng dụng không có cấu hình webpack và build xanh, mặc định mới là đường ít bảo trì hơn — đó là đường mà Next.js sẽ kiểm thử kỹ nhất ở các bản sau.
Tự đo trên ứng dụng của bạn
1. Chốt một commit, không đổi gì giữa hai lần đo. 2. Xoá .next trước mỗi lần build, nếu không bạn đang đo cache. 3. Ba lần mỗi bundler, xen kẽ, lấy trung vị — một lần chạy duy nhất trên máy đang chạy Docker sẽ nói dối. 4. So nội dung trang sau khi build, không chỉ thời gian: curl một trang tĩnh và một trang có tham số trên cả hai bản production. 5. Ghi lại dung lượng .next; nó ảnh hưởng thời gian deploy và dung lượng đĩa trên VPS. 6. Với dev, đo ba thứ: mở lần đầu, một route lạ, và một lần sửa tệp — con số thứ ba là con số bạn sống cùng.
Script đo của trang này làm đúng sáu việc đó và in ra từng bước. Nó không khẳng định Turbopack nhanh hơn; nó ghi lại cái đo được, và báo lỗi nếu hai bundler build ra hai ứng dụng khác nhau. Đó cũng là cách đọc mọi bài benchmark, kể cả bài này.
Đã kiểm tra
- 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
Script đo (chưa công khai): bun scripts/guides/verify-turbopack.ts
Liên quan
- Nâng cấp lên Next.js 16: những gì thật sự làm build dừng
- next/image: tối ưu ảnh, đo trước và sau
- Lỗ hổng bảo mật Next.js
- Kiểm tra phiên bản Next.js của trang bạn
Nguồn
- Next.js docs — Turbopack (what it supports, what still needs webpack)
- Next.js docs — turbopack config (rules, resolveAlias, top-level in 16)
- Next.js blog — Next.js 16: “Turbopack (stable): Default bundler for all apps”
- Next.js docs — the next CLI (build, dev, --webpack)
- npm registry — next dist-tags
Tóm tắt và diễn giải bằng lời của chúng tôi; mã viết lại từ ví dụ trong tài liệu Next.js. Khi có khác biệt, tài liệu Next.js là chuẩn.