Web Development

Rolldown vs Turbopack vs Rspack: The Speed Was Never the Hard Part

We moved four client builds onto Rust bundlers this year. Not one of them was hard because of the bundler. Every one was hard because the output changed shape — and output shape breaks things your test suite cannot see.

KAKabir AnandLead Developer
11 min read
Flat vector illustration of a fast-spinning industrial rotor feeding a narrow output chute where stacked parcels have shifted out of order

A client's marketing site went from a 4m10s production build to 38 seconds. We shipped it on a Tuesday and everyone was pleased. On the following Monday their support lead asked why the site had been flashing unstyled text on first load for a week, and why every JavaScript error in Sentry now pointed at line 1 of a file called index-B4kQ.js.

Nothing about that week had to do with the bundler being fast. The bundler was fast. It also emitted CSS in a different order, split chunks along different boundaries, and produced sourcemaps our release upload step no longer matched. Three regressions, all invisible in dev, all green in CI, all shipped.

We've done four of these migrations this year — two onto Vite 8 with Rolldown, one onto Rspack, one that came free with a Next.js upgrade. The performance numbers were real every time and decided nothing. What decided the work was whose plugin API we inherited and how far the output moved.

What actually ended was the two-bundler tax

For most of Vite's life you were running two different bundlers without thinking about it. esbuild transformed your modules in dev, Rollup produced the production build, and the two disagreed in small ways: interop with CommonJS dependencies, decorator and class-field ordering, how a circular import resolved, whether a side-effectful module got dropped. Vite 8 went stable in March 2026 and replaced that split with a single Rust pipeline in Rolldown — the project calls it its biggest architectural change since Vite 2.

Every team we work with has lost at least a day to that split. The shape is always the same: a bug that only exists in the production build, no stack trace worth reading, and a debug loop that costs a full build per attempt. You end up bisecting a 4-minute build with console.log, which is not engineering, it is waiting.

The build was never slow enough to be a priority. It was inconsistent enough to be a tax, and nobody was writing that down.

Our own retro, after the second migration

That is the honest headline for 2026. Vite reports up to 10–30× faster builds, and we measured 6.6× on the largest app we moved — but the number that changed how the team works was the one nobody publishes: dev and prod now fail the same way. A bug you can reproduce in a hot-reload loop is a bug you fix in ten minutes instead of two hours.

Four bundlers, and only two of them are a choice

These get compared as if they were four options on a shortlist. They aren't. Two of them are decided by a framework you already picked, one is decided by a config file you can't delete, and only one is a genuine adoption decision most weeks. Sorting them that way makes the comparison much shorter.

Rolldown — the one you get by upgrading Vite

Rolldown is a Rust bundler that speaks Rollup's plugin interface, and in Vite 8 it is the whole pipeline: dev, build, and the dependency pre-bundling esbuild used to handle. For an app on Vite this is an upgrade, not an adoption — you bump a version and most projects build on the first try.

The compatibility claim holds better than we expected, with one clear boundary. Plugins that use the documented hooks — resolveId, load, transform — moved without edits across all three codebases we tried. Plugins that reach into Rollup's internals, walk the module graph during generateBundle, or mutate chunk metadata are where the work is. In our case that was one in-house plugin doing route manifest generation, and it took an afternoon.

Turbopack — you don't pick it, Next.js picks it for you

Turbopack is not on the shortlist because there is no shortlist. If you're on a current Next.js, it builds your app, and the interesting question isn't whether it's faster than Rspack — it's how much control you gave up. The webpack escape hatch that used to absorb odd requirements is not the road ahead, and a good deal of the bundler's behaviour is framework policy rather than your configuration.

We think that trade is usually correct and we take it deliberately. The framework owning the bundler is why route-level code splitting, server/client boundaries and the caching model work without a config file. It is also why, when a build does something surprising, your debugging options are a GitHub issue rather than a plugin. Price that in before you put an unusual asset pipeline inside a Next app.

Rspack — for the webpack config nobody is allowed to rewrite

Rspack exists for a specific, extremely common situation: a webpack config that has grown for six years, that three people understand, and that contains at least one loader doing something load-bearing and undocumented. Rspack keeps the webpack API and swaps the engine underneath, so the migration is a config compatibility exercise rather than a rewrite.

It is the least glamorous option here and the one we have recommended most often on takeover projects. Compatibility is close but not total — the loaders that broke for us were the ones calling into webpack internals or doing their own AST work, the same category that breaks on Rolldown. If your config is mostly standard loaders and a handful of well-known plugins, budget days. If it has custom loaders written by someone who left, budget weeks and read them first.

esbuild — still everywhere, just not your app bundler

esbuild didn't lose. It moved. It is still the fastest way to turn TypeScript into JavaScript, and it is still sitting inside your test runner, your CLI build, your Lambda packaging step and half the tools you use daily. What it stopped being is the thing that assembles an application, and that was always by design: its plugin API is deliberately narrow, with no full AST access, because that narrowness is where the speed comes from.

Reach for it directly when you're shipping a library, a CLI or a serverless function — anything where the job is transform-and-emit and the output has one consumer. Don't reach for it when you need chunking strategy, CSS handling and asset hashing to cooperate. That's not a limitation anyone is hiding; it's the trade the tool announced on day one.

Rolldown (Vite 8)TurbopackRspackesbuild
Written inRustRustRustGo
How you adopt itUpgrade ViteUse Next.jsPoint it at your webpack configIt's already in your toolchain
Plugin surface you inheritRollup hooksNext's, not yourswebpack loaders and pluginsNarrow by design, no full AST
Same engine in dev and prodYes — the actual winYes, within NextYesN/A — it's a transform step
Where the migration hurtsPlugins using Rollup internalsNothing to migrate, little to steerCustom loaders touching internalsAnything needing real plugin power
Output shape movesChunk boundaries and CSS orderFramework's call, not yoursClose to webpack, not identicalN/A
Who owns a regressionYou and the Vite teamThe frameworkYou and the Rspack teamYou
Choose it whenYou're on ViteYou're on NextThe config is older than the teamYou're building a library or CLI
Isometric illustration of two parallel conveyor lines merging into a single wider line feeding one output stack
The dev pipeline and the production pipeline stop being two pipelines. That is worth more than the multiplier on the build time.

The three things that actually break

A bundler swap produces a class of regression your test suite is structurally unable to catch. Unit tests run against source. Component tests run against a dev transform. End-to-end tests run against a browser that will happily render a correct page from a badly-shaped bundle. Everything passes, and the damage lands in your CDN metrics and your error tracker instead.

  • Plugin hooks that used private surface— the documented hooks port cleanly across all of these. The ones that walked the module graph or edited chunk metadata are where the days go, and they’re usually in-house code nobody has opened since it was written.
  • Chunk boundaries and CSS order— a new splitting strategy means new filenames on files that didn’t change, which means a cold CDN and a cold browser cache for every returning visitor on release day. Different CSS emission order means specificity ties resolve the other way.
  • Sourcemap fidelity and upload— maps that still exist, still validate, and no longer line up with what your release pipeline uploaded. Nothing errors. Your stack traces just quietly become useless.

The sourcemap one cost us the most and looked like the least. Sentry kept accepting events, the release still had artefacts attached, and every frame resolved to the top of a hashed file. For six days the team read production errors as if they were minified, assumed the errors were new, and chased two of them into the wrong subsystem. Nothing in the system was in a failed state — it was in a wrong state, which is worse, because failed states page someone.

The CSS one is the one to check first, because it is the one users see. Under Rollup our vendor styles landed before our utility layer; under the new pipeline two chunks swapped order and a handful of equal-specificity rules started resolving the other way. On a design system with tight token discipline this is a non-event. On a codebase carrying five years of overrides, it is a visual regression on pages nobody screenshots.

The pre-flight we run now

After the second migration we stopped treating this as a version bump and started treating it as an output-shape change, which is what it is. The checks below take about half a day to set up once and have caught something on every migration since.

Before you merge a bundler swap

  • Build both ways and diff the manifest: chunk count, entry-to-chunk mapping, and the emission order of every CSS file.
  • Take the ten most-visited routes and diff rendered HTML and computed styles between old and new builds, not screenshots of them.
  • Throw a deliberate error in staging and confirm the stack trace in your error tracker still resolves to a real file and line.
  • Check total transferred bytes per route, not total bundle size — a smaller bundle split worse is a slower page.
  • Grep your plugins and loaders for anything importing from a bundler's internals; read those before you start, not when they fail.
  • Plan the release for a low-traffic window and expect a cold-cache spike — every hashed filename that changed is a fresh download.
scripts/check-bundle-shape.mjs
1// Runs in CI on every build. It does not measure speed —
2// speed is visible. Shape is not.
3import { readFileSync } from "node:fs";
4import { summariseOutput, diffShape } from "./bundle-shape.mjs";
5 
6const baseline = JSON.parse(readFileSync("bundle-baseline.json", "utf8"));
7const current = summariseOutput("dist"); // chunks, entry map, css order
8 
9const drift = diffShape(baseline, current);
10 
11if (drift.length) {
12  // Not a failure. A decision that needs a human and a changelog line.
13  console.error("bundle shape changed:", drift);
14  process.exit(process.env.ALLOW_SHAPE_DRIFT ? 0 : 1);
15}

It is a hundred lines and it is not clever. What it buys is that output shape becomes something you approve rather than something you discover — and it keeps earning after the migration, because dependency upgrades move chunk boundaries too. Two of the three times it has fired since, the cause was a new package, not a bundler.

Editorial photograph of two near-identical machined metal parts laid side by side on a workbench with a pair of calipers between them
Both builds are correct. The question is whether they are the same shape, and that has to be measured rather than assumed.

The short version

If you're on Vite, upgrade to 8 and take Rolldown — the single-engine consistency is worth the afternoon it may cost you, independent of the speed. If you're on Next, you already have Turbopack and the useful work is auditing anything unusual in your asset pipeline before it becomes someone else's policy. If you're carrying a webpack config older than half the team, Rspack is the pragmatic answer and the alternative is a rewrite you won't get funded. And keep esbuild exactly where it is, doing the job it's best at.

What we'd push back on is the framing these tools get sold with. Build speed is a developer-experience improvement and a CI bill, and both are worth having. It is not a user-facing performance win, and treating it as one is how a team ends up shipping a 6× faster build that made the site slower to load because the chunks split worse.

Run the swap on a branch, build it twice, and diff the two dist directories before you look at a single timing number. If the shape moved, you now know the thing your tests were never going to tell you. If it didn't, take the speed — you've earned it cheaply.

Keep Reading

More from the blog

The Engineering Partner You Can Build On

Reliable software takes an experienced team that owns delivery end to end. Here’s the track record behind ours.

11+

Years Building Custom Software

320+

Projects Delivered Across Web, Mobile & AI

85%

Repeat Client Rate

12+

Countries Served Worldwide

Trusted by startups and enterprises worldwide

Work With Us

Let’s create
with purpose

Share your goals, timeline, and challenges — we’ll respond with clarity and next steps.

Ambitious ideas deserve thoughtful execution. Start the conversation and let’s define what success looks like.

Team

Acetrum

Est. 2015

4.9/5

Trusted by
top brands

Services interested in:

By submitting, I confirm I’ve read and agree with Privacy and Cookie Policies.

Newsletter

Signals worth
paying attention

No recycled headlines — just the patterns we’re seeing across real client work, distilled into one read a month.

A curated digest of practical thinking and real-world brand perspectives monthly.

No spam. Unsubscribe anytime.