icy.dev
← all posts
#vue#compilers#performance#measurement

Vue’s compiler makes your SVG icons a little bigger

There’s a threshold buried in Vue’s template compiler that nobody has touched in years. I measured it. On real feather icons it makes your bundle 4.2% bigger, which is the opposite of what it’s meant to do.

Sam KavanaghSenior Fullstack EngineerAug 22, 2026 · 6 min read

Ok so this is a fun little one. Vue takes your template and turns it into JavaScript instructions before it ever reaches a browser. For the bits of your page that never change (a footer, an icon set, a static table) it has two ways to write those instructions down.

Way A, the list: emit a separate createElementVNode call for every element. Lots of little instructions.
Way B, the sticky note: squash the whole chunk into one createStaticVNode("…html…") call, so it’s a single blob of HTML the browser pastes in one go.

Vue picks between them with a hardcoded rule that lives in stringifyStatic.ts:

tsexport enum StringifyThresholds {
  ELEMENT_WITH_BINDING_COUNT = 5,
  NODE_COUNT = 20, // use the sticky note once a run hits 20 nodes
}

Twenty. Why twenty? No idea, and I don’t think anyone else does either. It’s just been sitting there, and the git history only ever touches it to fix correctness bugs, never to check whether 20 is actually a good number for output size. So I checked.

The test

I patched the threshold, compiled a bunch of static-heavy templates at every setting, and ran brotli (max quality) over each result. First pass across the whole set? Basically nothing, about 0.8%. By my own rules that’s a “move on, nothing here”.

But the average was hiding everything. Once I looked at it per template, the decision swings hard in both directions, and the wins and losses were quietly cancelling each other out:

measured · brotli, max quality

What stringifying does to compressed size
SVG icon sprite (real feather icons)
+4.2%
long prose (synthetic)
+4.1%
SVG icon
+3.3%
icon sprite
+2.8%
feature grid
+0.6%
data table
+0%
big footer
-4.1%
marketing footer
-4.7%
← smaller = winbigger = the compiler pessimised you →

Look at that split. Stringifying a link-heavy footer is a real win (−4.7%). But do it to an SVG icon or a paragraph of prose and the compressed output gets bigger, up to +4.2%. The compiler is confidently making things worse.

Why the SVGs lose

It comes down to compression. An SVG path’s d="…" is a big messy string of numbers. Shoved into a sticky-note HTML blob, all those quotes and decimals just sit there. Left as a list of vnode calls, the repeated createElementVNode("path"… scaffolding compresses really well, because brotli loves the repetition. So the list wins, and stringifying throws that win away.

And this isn’t just my toy templates. I grabbed actual feather icons and built a sprite out of them. Shipped Vue stringifies it and it comes out 4.2% bigger after brotli. Icon sprites are everywhere, so this is a small tax a lot of sites are quietly paying.

Quick honesty note: my made-up prose looked bad too, but when I tested a real, longer Wikipedia article it actually won by 2.1%. Real data corrected me. The one that reliably loses is SVG path data.

But isn’t it faster?

Fair question. Stringifying trades size for runtime, and a big innerHTML parse can beat creating a hundred vnodes. So I measured that too, in a real browser. Turns out only the big structural runs (like an 81-node footer) get a real speed win. The SVG and prose chunks that lose on size get no speed-up to make up for it. So there it’s not a trade at all, it’s just worse.

So what’s the fix?

Counting nodes is the wrong signal. It can’t tell a chunk of many little links (sticky note wins) from a chunk of chunky SVG squiggles (list wins). The right call is to compare the estimated compressed size of both forms per chunk. I tried a cheap version of that, deciding with gzip instead of brotli, and it’s not faithful enough (it gets the footers wrong). A proper fix needs the real compressor at build time. Doable, just not free.

This isn’t a “Vue is slow” thing. The modern Vue compiler is really good. It’s just that one unexamined constant quietly makes your SVG-heavy static chunks ~4% bigger for no benefit, and it’s been hiding in plain sight.

Small win. But I’ll take “found a real, measurable thing the tool gets wrong” over a hunch any day. Next up on the table: Vue Vapor, and the tax nobody mentions.

The harness, the corpus, and the full running log (dead ends included).


Measured against @vue/compiler-dom 3.5.41, brotli at max quality, cross-checked against a slow reference. I built this one with Claude Code doing a lot of the heavy lifting on the harness while I set the questions and poked holes in the method. Every number is reproducible from the repo above.