← Writing

Cutting 3.7 MB of icon fonts to 48 KB

How a Flutter framework upgrade exposed that we were shipping ~7,447 icons to use 52 of them — and the two-stage fix that got a vendored icon font from 1.22 MB down to 3.8 KB.

On this page

This started as a build breakage and ended as a ~99% reduction in icon-font weight — from roughly 3.7 MB to 48 KB. Along the way it turned out we’d been shipping ~7,447 icons to production for years while actually using 52 of them.

If your Flutter app depends on a big icon pack, there’s a decent chance you’re doing the same thing right now. Here’s how to find out, and how to fix it.

The breakage that exposed everything

Flutter 3.44 made IconData final. That’s a small, reasonable framework change — unless you depend on a package that subclasses or otherwise fights with IconData, which our icon pack did. The package had been abandoned since 2023, so there was no upstream fix coming.

Fixing the compile error meant actually reading how our icons were built and bundled. That’s when the real find surfaced: our release builds were passing --no-tree-shake-icons.

Why the tree shaker was off

Flutter’s icon tree shaking is one of the best free wins in the toolchain: at build time it scans for const IconData usage and strips every glyph you don’t reference out of the bundled fonts. It works automatically — if every IconData construction it can see is const.

Ours weren’t. Three IconData constructions in the PDF-theming code were built at runtime rather than as const values. The tree shaker can’t prove which glyphs those might reference, so it refuses to run, and at some point in the project’s history someone had “fixed” the resulting build error the fast way: --no-tree-shake-icons.

That flag doesn’t just skip three icons. It ships every glyph of every icon font in the app. In our case, all ~7,447 glyphs of the icon pack plus the full weight of every other font — for the 52 icons the UI actually rendered.

Stage one: make everything const

The first pass was mechanical: rewrite the three non-const IconData constructions so the tree shaker could see through them, drop --no-tree-shake-icons, and rebuild.

Result: 5 of the 6 bundled fonts shrank by ~98%. A clean, huge win — with one anomaly. The vendored icon pack, the biggest font of all, only dropped 19%. Still over a megabyte for a few dozen icons.

Stage two: the anomaly

The tree shaker isn’t magic; it’s static analysis. It keeps a glyph when it can see a const IconData referencing it. Our vendored pack exposed icons through runtime string-map lookups — call a function with an icon name, get an IconData back from a map. To static analysis, every entry in that map is potentially reachable, so nearly everything survives shaking.

The fix was to make the usage statically visible: replace the string-keyed lookups with const getters for the icons we actually use. Once every icon reference was a const the compiler could trace, the shaker did its job properly.

That single change took the vendored font from 1.22 MB to 3.8 KB — a 99.7% reduction on that one file — and brought the total icon-font payload to about 48 KB.

Why this matters beyond bragging rights

App size isn’t cosmetic. It’s install conversion, update friction on bad connections, and — for a product whose users work out of barns and parking lots on mobile data — a real usability property. Nearly 4 MB of dead glyphs was pure waste that no one had ever decided to ship; it accumulated from one abandoned package and one workaround flag.

The pattern generalizes: a suppression flag added to fix a build error is a loan, and the interest compounds silently. Nobody revisits --no-tree-shake-icons because nothing visibly breaks. It took an unrelated framework change forcing us into that code to notice.

The checklist for your own app

  1. Grep your build scripts and CI for --no-tree-shake-icons. If it’s there, someone once traded your app size for a quick fix. Find out why.
  2. Try removing it. The build error it suppresses will point you at every non-const IconData construction in your app. There are usually only a handful.
  3. Make those constructions const. If an icon is chosen dynamically, restructure so the set of possible icons is a const list the compiler can see, and select between const values at runtime.
  4. Audit icon packages for string-map lookup APIs. Any “get icon by name” function defeats tree shaking for the whole pack. Replace with direct const references, or vendor the pack and expose const getters for the icons you use.
  5. Measure before and after. Check the font assets in your built bundle, not just the total app size — that’s where you’ll see the 98% drops (and catch the one font that didn’t drop, which is where your remaining dynamic lookup lives).
  6. Count your actual icon usage. If the number is “dozens” and your icon dependency is “thousands,” treat the pack as a source to copy const definitions from, not a runtime dependency.

The whole fix was a day of work, most of it investigation. The 3.7 MB had been shipping for years.

← All writing Book a call →
Book a call → WhatsApp