Rust ate.
I started learning frontend in 2018. Back then people were still fighting over React vs Vue vs Angular, each with its own camp. Picking a framework meant picking a side.
The framework itself was fine, since the official docs cover how to write and assemble your HTML. The annoying part was the pile of build config underneath: the webpack config, one loader after another, figuring out which babel presets to add. Learning frontend back then was mostly reading official docs, and the moment some special loader showed up you'd start over. So annoying. Oh, and plugins, install as needed.
Then Next.js showed up. It wrapped that whole layer. No more webpack config, no more ordering loaders, create-next-app and it just runs. So good. From then on I barely touched build config, and avoided it whenever I could. For the next few years I just stayed there: the framework handled the bottom, I wrote components on top, and build was a black box that just worked. I didn't chase it, didn't feel like I needed to.
Until SWC and Turbopack started showing up all over Next.js ecosystem articles, and I figured maybe I should actually sort out how this whole thing evolved. You can't let Next.js hand you a worry-free life and then just... live worry-free forever.
build (next build, or whatever build) has three roles
And they're exactly the three things I used to configure by hand:
- parse + transform: turn my TS / JSX into JS the browser can run. Used to be Babel.
- bundle: resolve all the imports, walk the graph, stitch it into a few files. Used to be Webpack.
- minify: shrink the output. Used to be Terser.
The few years I wasn't watching the black box, all three became Rust LOL.
From Button.tsx to app.382f3f.js
From Button.tsx to the shipped file, it goes through:
Button.tsx
│ parse + transform (SWC / esbuild / Babel)
│ strip TS types, JSX → JS, lower new syntax
▼
Button.js (plain JS, no types)
│ bundle (Turbopack / Vite / Rolldown)
│ follow imports, walk the dep graph, merge
▼
one big module graph
│ minify (SWC / esbuild / oxc / terser)
│ strip whitespace, rename vars, drop dead code
▼
app.382f3f.js ← 382f3f is a content hash, for cache-busting
(off to the side, NOT in this pipeline)
tsc --noEmit → checks types, emits nothing
The 382f3f is a hash computed from the file's contents. Change the code and the hash changes, so the filename changes, so the browser downloads the new file instead of using the old cached one.
SWC comes for Babel
SWC was written by a Korean developer, DongYoon Kang, known online as kdy1, nicknamed Donny. He started it back in 2017 as a Babel alternative. Same job (parse + transform), but in Rust and multi-threaded by default. The gap between 20x and 70x is core count: their own claim is 20x faster than Babel on a single thread, 70x on four cores.
Next.js 12, October 2021, swapped the default Babel for SWC. That's when the name SWC caught my eye, but it wasn't even new: Kang had already joined Vercel back in August 2021. I just wasn't looking.
Turbopack comes for Webpack
This one's better: it was written by the author of Webpack himself, Tobias Koppers, who's also at Vercel now. The same author wrote webpack's successor in Rust, with SWC underneath. Turbopack for next dev went stable in Next.js 15 (October 2024), and since Next.js 16 (October 2025) it's the default for next build too. To keep using webpack, add --webpack.
SWC comes for Terser, too
Since v13, Next.js even does minification with SWC by default. Those minify settings I used to tweak to save a few KB, the framework just decides for me now.
Oh, and Vite's everywhere now
Vite (2020) uses esbuild for dev (that one's written in Go, by Figma's Evan Wallace) and Rollup for the production build. Rollup later got rewritten in Rust as Rolldown, and by Vite 8 (2026) it ships as the default.
What each framework uses by default: Vite, except Next.js
Checking the current state, almost every framework defaults to Vite, and only Next.js uses its own Turbopack.
| Framework | Default build tool |
|---|---|
| Next.js | Turbopack (default for dev and build since v16; add --webpack to use webpack) |
| Remix | Vite (stable since v2.7.0, 2024) |
| React Router v7 | Vite (framework mode; Remix merged into it) |
| Vue (create-vue) | Vite (Evan You made both) |
| Nuxt | Vite (webpack / rspack optional) |
| SvelteKit | Vite |
| Astro | Vite |
| Angular (v17+) | esbuild for the build, Vite for the dev server |
| SolidStart | Vite (via Vinxi) |
| Qwik | Vite |
| Create React App | webpack + Babel, deprecated in 2025, now points you at Vite / Parcel / Rsbuild |
Angular is different: it builds with esbuild and serves with Vite. Earlier I said Vite uses esbuild for dev; that's esbuild inside Vite. Angular takes only Vite's dev server and calls esbuild directly for the build instead of Vite's Rollup, so here the two are separate pieces side by side. And no, Next.js + Vite was never a thing, Next ships its own bundlers only. Turbopack and Parcel were never combined either; separate projects that don't touch.
That makes sense, because Vite isn't tied to any framework and anyone can plug into it, so everyone did. Next.js, being big enough and Vercel-funded enough, runs its own.
What about benchmarks?
By 2026 these tools are all fast enough that speed isn't the main reason to pick one; ecosystem, compatibility and maintenance matter more. And most of these benchmarks are published by one tool's own team, so check who made them before reading the numbers. To look at the numbers yourself:
- rstackjs/build-tools-performance: the most comprehensive, regularly updated comparison (Rspack, Vite, Rolldown, esbuild, webpack, Rollup, Parcel, Farm). Maintained by the Rspack team.
- esbuild's own benchmark: esbuild vs webpack / Rollup / Parcel. Esbuild-favorable; the test setup is described in the FAQ, so you can check it (three.js ×10: esbuild 0.39s, Parcel 14.9s, Rollup+Terser 34.1s, webpack 41.2s).
Vite itself is rarely the thing being measured. It orchestrates esbuild (dev) and Rollup/Rolldown (prod), so a "Vite is fast" number mostly measures esbuild and Rollup/Rolldown underneath.
The thing that always confused me: why run it twice?
Back then one thing kept tripping me up: Babel already handles TypeScript, so why does the project run tsc on top of it?
Because I'd treated stripping types and checking types as the same thing:
- Strip types (TS → JS): remove the types so it can run. Babel, SWC, esbuild all do this.
- Check types: verify the types are correct. Only tsc does this.
Babel, SWC and esbuild only strip types; none of them type-check. Whether you need to run tsc separately depends on whether your setup includes type checking:
| Setup | Strip types (TS→JS) | Type check | You run |
|---|---|---|---|
| Next.js | SWC, built in | next build runs tsc for you | nothing |
| Vite | esbuild, built in | not included | tsc --noEmit (hence vue-tsc && vite build) |
| Bun | built in | not included | tsc --noEmit |
| Babel + Webpack (old me) | @babel/preset-typescript | not included | tsc --noEmit separately |
| Just tsc (small lib) | tsc | tsc, same run | tsc, does both, but no bundling |
tsc on its own does two jobs, it type-checks and emits .js. tsc --noEmit is the same check with the output turned off. Modern setups use --noEmit because a faster tool already produced the .js; only tsc's check is needed, not its slower output.
Next.js is the one that spoils you: it wraps all of it, so you just see next build :)
then Bun
Bun is another runtime going head to head with Node. It started out written in a language called Zig, and hit 1.0 in 2023. But when I went to look this time, it's migrating from Zig to Rust itself, core merged into main in May 2026. A piece about "Rust eating the whole toolchain," and even Bun, the example, got eaten by Rust halfway through.
then
There's a project called Oxc, by Boshen, taking it a step further: instead of every tool parsing the source over again, parse once and share a single AST. Parser, linter (oxlint), and transformer are already out, and Rolldown already uses Oxc's parser underneath. If it works, today's situation of SWC and Oxc each having their own parser might get pulled back into one.
The chronicle
~2015 Babel + Webpack + Terser era (JS processing JS)
2017 SWC started (DongYoon Kang), nobody using it yet
2018 Terser replaces UglifyJS as default minifier
2020 esbuild (Go) appears; Vite appears (dev=esbuild, prod=Rollup)
2021-08 Kang joins Vercel (Next 11.1)
2021-10 Next.js 12: SWC replaces Babel
2022 SWC minify lands in Next (default in v13)
2023-09 Bun 1.0 (still Zig back then)
2023-10 Rolldown announced (Rust port of Rollup)
2024-02 Remix goes Vite
2024-10 Next.js 15: Turbopack stable for next dev
2025-02 Create React App deprecated
2025-10 Next.js 16: Turbopack the default for builds too
2026-03 Vite 8: Rolldown as default
2026-05 Bun's core goes Zig → Rust
So
What actually happened inside that black box? The whole layer moved from JavaScript to native binaries, got redesigned, and started using multiple cores, so it got a lot faster. Where the speed comes from, and how much each part contributes, is measured in another post.
The "Rust is faster" idea kept growing on me, so on a whim I built a benchmark. Sometimes it was a tie, twice Node actually won, but mostly Rust was around three times faster. The 20-70x figures on SWC's site turned out, when measured, to come mostly from four cores and two differently-built programs, not the Rust language. The whole thing, with every chart, is its own post: I kept repeating 'Rust is faster.' So I sat down and measured it.. Each reason measured on its own is here: Rust vs V8.
And I felt none of it, because Next.js kept it covered the whole time. I still don't have to touch it. It's just that this time I at least know what's under there.
References
- SWC author DongYoon Kang (kdy1): https://github.com/kdy1
- SWC 20–70x faster than Babel: https://swc.rs/
- Next.js 12 defaults to SWC (2021-10): https://nextjs.org/blog/next-12
- DongYoon Kang joins Vercel (Next 11.1, 2021-08): https://nextjs.org/blog/next-11-1
- Turbopack (Tobias Koppers / Vercel / Rust / SWC): https://vercel.com/blog/turbopack
- Turbopack stable for
next dev(Next 15, 2024-10): https://nextjs.org/blog/next-15 - Turbopack default for builds (Next 16, 2025-10): https://nextjs.org/blog/next-16
- esbuild (Go, Evan Wallace): https://esbuild.github.io/
- Vite (dev=esbuild, prod=Rollup): https://vite.dev/guide/why
- Rolldown (Rust port of Rollup): https://voidzero.dev/posts/announcing-rolldown-1-0
- Vite 8 defaults to Rolldown (2026-03): https://vite.dev/blog/announcing-vite8
- SWC minify default in Next.js (since v13): https://nextjs.org/docs/architecture/nextjs-compiler
- Fast tools don't type-check, you need tsc: https://esbuild.github.io/content-types/
- Bun 1.0 (2023-09): https://bun.com/blog/bun-v1.0
- Bun core Zig → Rust (PR #30412, 2026-05): https://www.theregister.com/devops/2026/05/14/anthropics-bun-rust-rewrite-merged-at-speed-of-ai/ (current language breakdown confirmed via GitHub's language stats: https://api.github.com/repos/oven-sh/bun/languages)
- Oxc: https://oxc.rs/
- Remix goes Vite (v2.7.0): https://remix.run/blog/remix-vite-stable
- React Router v7 modes (framework mode = Vite): https://reactrouter.com/start/modes
- Vue create-vue (Vite): https://github.com/vuejs/create-vue
- Nuxt default builder (Vite): https://nuxt.com/docs/getting-started/introduction
- SvelteKit on Vite: https://svelte.dev/docs/kit/introduction
- Astro on Vite (Astro 6): https://astro.build/blog/astro-6/
- Angular build system (esbuild + Vite): https://angular.dev/tools/cli/build-system-migration
- SolidStart (Vite via Vinxi): https://docs.solidjs.com/solid-start/getting-started
- Qwik on Vite: https://qwik.dev/docs/advanced/vite/
- Create React App deprecated (2025-02): https://react.dev/blog/2025/02/14/sunsetting-create-react-app