Skip to content
Engineering9 min read

Performance budgets are a tradeoff, not a score

Every performance conversation eventually reaches the same fork: something that makes the site nicer also makes it heavier. Pretending that fork doesn't exist is how sites end up either sluggish or joyless.

The number most teams optimise is the wrong one

Lighthouse runs one simulated load on a throttled connection from wherever you happen to be. It is a genuinely useful debugging tool and a poor grade. A site can score 98 in the lab and fail Core Web Vitals in the field, because real users are on mid-tier Android phones on congested networks and the lab is not.

Field data — what Chrome collects from actual visits and Google actually uses — is aggregated over 28 days at the 75th percentile. That last detail matters more than people expect: three quarters of your visits must meet the threshold, which is a materially stricter bar than a median and specifically designed to catch the tail you would otherwise ignore.

The practical consequence is that chasing a lab score can make you feel finished while a quarter of your users are still having a poor experience. Watch CrUX, not Lighthouse, for anything you report.

If you only track one thing, track field LCP at p75. It correlates with revenue more reliably than any composite score.

Measure what crosses the wire

Bundle sizes on disk are not what anyone downloads. Everything is served compressed, and the gap is roughly threefold — a bundle that looks alarming at 860 KB on the filesystem is around 275 KB in transit.

This sounds like a technicality and it changes decisions. We have watched teams start emergency refactors off an uncompressed figure that, measured properly, was somewhat above target rather than catastrophic. The reverse also happens: a compressed number that looks fine can hide a parse-and-execute cost that hurts interaction responsiveness, because compression helps transfer and does nothing for main-thread work.

So track both, and know which question each one answers. Compressed size tells you about load. Uncompressed size and script count tell you more about INP.

Setting a budget you will actually keep

A budget nobody can meet gets ignored within a month. A useful one is specific, measured automatically, and set slightly above where you currently are so it constrains growth rather than demanding an immediate rewrite.

  • Express it in gzipped KB per route

    Not a score, not a total across the site. Per route, because that is the unit a visitor experiences.

  • Measure it in CI from the real build

    Parse the prerendered output for the scripts each route actually loads. Manual checks drift the week after someone sets them up.

  • Separate first load from everything else

    Prefetched and route-level chunks are not the same as what blocks the first interaction. Counting them together inflates the figure substantially.

  • Report breaches, don't always fail on them

    A hard failure is right for correctness checks. For a tradeoff, it blocks work on a decision that needs a human.

Where the bytes usually go

On a modern React site the framework runtime is largely fixed cost and the interesting variance is in what you added. Animation libraries, date and internationalisation utilities, charting, rich text editors, and analytics stacks are the usual heavy items.

Animation libraries are the most common one worth arguing about, because they are frequently pulled into the root layout by a single component — an animated header, say — which puts them on every route including the ones with no animation at all.

That is a real tradeoff rather than a bug. A scroll-driven CSS implementation costs almost nothing and degrades to 'element simply appears' in browsers that do not support it. A JavaScript library animates consistently everywhere and costs perhaps forty to fifty kilobytes compressed. Neither answer is wrong; picking one without knowing you picked is.

Making the decision explicitly

The framing we use with clients is deliberately blunt: what did you buy with those bytes, and would you buy it again knowing the price?

Sometimes the answer is obviously yes. A checkout that feels responsive, an interactive tool that does real work, a motion system that makes a brand feel considered — these earn their weight, and stripping them to hit an arbitrary number makes the product worse in exchange for a metric nobody outside the team sees.

Sometimes it is obviously no. A carousel library for one static image. A charting package for a single number. Three analytics tools measuring the same event. These are the ones to find first, and they are usually worth more than any refactor of something you actually use.

The point of a budget is not to win. It is to make sure the tradeoff was chosen rather than accumulated.

We do this for a living

If you'd rather not build this yourself, these are the services where it lives.

Rather not DIY

We'll run this on your account.

A free 30-minute teardown of your funnel, ads, and analytics. You keep the findings whether or not we work together.