I stopped touching build config, and the whole layer got rewritten in Rust
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.
But the framework was never the annoying part. Frameworks tell you 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) is really three small roles
And they're exactly the three things I used to configure by hand:
- parse + transform: turn my TS / JSX into JS the browser understands. 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
Concretely, here's what happens to one file:
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 just a fingerprint of the file's contents. Change the code, the hash changes, the browser knows to re-fetch.
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, multi-threaded by default, and 20 to 70 times faster than Babel by their own numbers.
Next.js 12, October 2021, swapped the default Babel for SWC. Those two words caught my eye, but they weren'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 guy rewriting his own thing 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, with webpack now an opt-in --webpack flag.
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.
Who uses what — and how Vite quietly won everything but Next
Once I started looking, a pattern jumped out: almost every framework defaults to Vite now. The one holdout is Next.js, running its own thing.
| Framework | Default build tool |
|---|---|
| Next.js | Turbopack (default for dev and build since v16; --webpack to opt out) |
| 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 |
So the map is basically: Vite everywhere, Turbopack in Next.js. Angular is the one hybrid (esbuild builds, Vite serves). That might look like it contradicts "Vite uses esbuild for dev" from earlier, but it doesn't. Normally esbuild lives inside Vite. Angular instead takes only Vite's dev server, and calls esbuild directly for the build rather than going through Vite's Rollup path, so here the two really are separate pieces wired 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.
Which makes sense: Vite is a framework-agnostic layer anyone can build on, and most people did. Next.js is big enough, and Vercel-funded enough, to run its own.
What about benchmarks?
Short version: by 2026 they're all fast enough that raw speed isn't the deciding factor anymore, ecosystem and compatibility and maintenance are. And every benchmark is published by one of the tools' own vendors, so read the numbers knowing who made them. If you want to dig in yourself:
- rstackjs/build-tools-performance: the most comprehensive, regularly updated comparison (Rspack, Vite, Rolldown, esbuild, webpack, Rollup, Parcel, Farm). Note it's maintained by the Rspack team.
- esbuild's own benchmark: esbuild vs webpack / Rollup / Parcel. Esbuild-favorable, but the numbers are real (three.js ×10: esbuild 0.39s, Parcel 14.9s, Rollup+Terser 34.1s, webpack 41.2s).
And one thing worth keeping in mind: Vite itself is rarely the thing being measured. It orchestrates esbuild (dev) and Rollup/Rolldown (prod), so a "Vite is fast" number is really the Rust bundler underneath doing the work.
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 mashed two different things into one:
- Strip types (TS → JS): remove the types so it can run. Babel, SWC, esbuild all do this.
- Check types: verify you didn't write them wrong. Only tsc does this.
The fast tools each do their own job and skip the check. So it comes down to whether type handling is baked into your setup or a separate package:
| 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 |
One thing worth pinning down: 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; you only want tsc for the check, not its slow 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. If it works, the current everyone-does-their-own-thing situation (SWC here, Rolldown there) 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? It didn't get smarter. The whole layer just got rewritten from JavaScript into Rust, and got a lot faster. Why Rust makes it faster: the old tools are JS, and JS has to run inside Node's V8 engine first. Rust compiles straight to native machine code and runs on the CPU directly, no V8 in between. Same file-crunching work, just faster.
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