Field Notes
All posts

Putting your JavaScript on a diet

Bytes of script cost more than bytes of anything else. A practical order of operations for shipping less.

Why script is expensive

A kilobyte of JavaScript costs more than a kilobyte of image. The image is decoded off the main thread and displayed. The script must be downloaded, parsed, compiled, and executed, and the last three steps compete with every interaction the visitor tries to make. On a mid-range phone, a megabyte of JavaScript can occupy the main thread for several seconds.

Step one: measure what you ship

Generate a bundle analysis and look at it honestly. Most teams find at least one surprise: a date library that pulls in every locale, an icon set imported whole, two versions of the same dependency, or a polyfill for a browser nobody supports anymore.

Step two: remove what is unused

Delete dead code, unused dependencies, and features that were rolled back but never removed. This is the cheapest step and often the most effective.

Step three: replace what is heavy

For each large dependency, ask whether a smaller one would do. Many popular utility libraries have lightweight alternatives, and many tasks they handle are now covered by the platform itself: fetch, Intl for formatting dates and numbers, structuredClone, and native form validation.

Step four: split what remains

Code that is only needed on one route should only load on that route. Code that is only needed after an interaction, such as a rich text editor or a chart library, can load when the interaction happens. Modern bundlers make route-level splitting nearly automatic; interaction-level splitting needs a dynamic import at the right place.

Step five: render on the server

If a component only displays content and never changes on the client, it does not need to ship as JavaScript at all. Server rendering frameworks increasingly let you mark components as server-only, sending their HTML and leaving their code behind.

Step six: hydrate less

Full-page hydration re-runs every component on the client to attach event handlers. Partial or progressive hydration attaches handlers only to the interactive parts, and only when needed. For content-heavy pages, this can cut main-thread work dramatically.

Keep it off

After the diet comes maintenance. Add a size check to CI that fails when a bundle grows past its limit, and review new dependencies for size before adding them. It is far easier to avoid adding 100 kilobytes than to remove it six months later.

The payoff

Sites that cut their JavaScript by half typically see interaction responsiveness improve on low-end devices more than on high-end ones, which is exactly where the improvement matters most.