Should you use Vue Vapor? I measured the tax
Vapor mode is the shiny new no-virtual-DOM compiler in Vue 3.6. Everyone says “20 to 50% smaller”. Nobody mentions what happens the second your app isn’t 100% Vapor. So I measured it.
Vue 3.6 ships Vapor mode, a new way to compile components that skips the virtual DOM completely and writes direct DOM operations instead. So now there are two engines in the box:
VDOM, the old way: every component leans on one shared runtime engine. It’s heavy, but you only pay for it once.
Vapor, the new way: no shared engine. Each component carries its own lean DOM code, but you lose that shared discount.
My plan was to build a little tool that finds the tipping point. Few components and Vapor is smaller, loads of components and the old VDOM wins, right? So I bundled real apps at every size, both ways, and weighed the brotli output.
Plot twist: there is no tipping point
Vapor is smaller at every app size, and it pulls further ahead the more components you add. Lower fixed runtime cost, and a lower per-component cost too (38 vs 49 bytes each). On pure single-mode size, Vapor just wins. My favourite idea for a tool died right there on contact with the data. 🪦
Which would be a pretty boring post. But killing that obvious question turned up a much better one.
The tax nobody mentions
Here’s the catch: you usually can’t go 100% Vapor. Some component needs an old feature Vapor doesn’t support yet, or a third-party library that only speaks VDOM. And the second you keep one VDOM component in a Vapor app, you’re shipping both engines plus an interop layer to glue them together.
Have a drag of this. Watch what happens the instant you keep one VDOM component:
measured · brotli, 40-component app
40.7KB
pure vapor
Smallest of all. One engine, no interop. 🧊
That’s a cliff, not a slope. About 20KB of brotli, paid in full the moment your app goes mixed. And it’s flat whether you’ve got 1 stubborn VDOM component or 20, because the heavy bit is the engine, not the components. It gets worse: a mixed app is bigger than either pure mode. Half-migrated is the single worst place to be for bundle size.
So the savings from migrating are a step function. Converting components saves you basically nothing until you convert the last VDOM holdout, and then the whole VDOM engine drops off in one go. All or nothing.
“So Vapor’s pointless unless you go all-in?”
That was my first thought too, and it’s wrong, because I was only looking at bytes. The whole point of Vapor is speed. So I measured that in a real browser: 500 rows, 200 update cycles, both engines, same reactive primitives.
measured · 500 rows × 200 update cycles, real browser
About 20% faster updates, steady across three runs (−21.5%, −18.9%, −20.1%), plus a faster mount. And the important bit: unlike the size win, this speed-up kicks in per component, right away, even in a mixed app. Every component you convert gets faster on the spot.
(I also tried to measure memory, but the browser handed me obvious garbage without precise-heap mode turned on, so I binned the number rather than make something up.)
The honest, two-axis answer
| Pure Vapor | Mixed (a few VDOM left) | |
|---|---|---|
| Size | smallest | worst, pays the ~20KB both-engines tax |
| Speed | ~20% faster | still ~20% faster on every Vapor component |
Use Vapor for speed. It pays off per component, mid-migration and all. Just know you’re carrying a fixed ~20KB “both engines” tax until you finish the job. Don’t reach for Vapor to save bytes unless you can go fully pure, because half-way is the biggest app of the lot.
That “Vapor is 20 to 50% smaller” line everyone repeats is only true at the finish line. The interesting story is the whole messy middle, and that’s exactly where a lot of teams are about to find themselves.
The bundling harness, the runtime benchmark, and the full running log.
Measured on vue 3.6.0-rc.5, esbuild bundling against the shipped runtime-with-vapor build, brotli max quality, runtime timings in real Chrome. The ~20KB is build-specific, the cliff shape isn’t. I built this with Claude Code doing most of the harness work while I steered and, in this case, argued with it until my original idea fell over. Reproducible from the repo above.