A performance budget is a number in CI, not a good intention

Sites get slow one reasonable pull request at a time. The fix is not a quarterly audit — it is a threshold that fails the build the day the regression lands.

6 min read Thrivee

No single change makes a web application slow. A charting library here, a font weight there, an analytics tag somebody added for a two-week experiment and never removed. Every one of those was a reasonable decision on its own day, and the sum is a 400 KB JavaScript bundle nobody chose.

Quarterly performance audits do not fix this. They produce a document, a sprint of clean-up, and a curve that starts climbing again the following Monday. The only thing that holds is a number that fails a build.

What to put a number on

Pick three, not ten:

  1. Transferred JavaScript on the critical path, in KB, compressed. This is the one that actually predicts how a mid-range Android phone feels.
  2. Largest Contentful Paint on a throttled profile, on your two or three most-entered pages.
  3. Total page weight, as a backstop, because images escape every other check.

Set the initial values at wherever you are today, not at a target. The point is to stop the drift, and you can ratchet down afterwards. A budget set at an aspirational number gets disabled within a fortnight, and a disabled budget is worse than none because it teaches everyone the check is theatre.

Make it fail loudly, and make it easy to argue with

Two properties matter more than the exact numbers.

The build fails. Not a warning in the log, not a comment nobody reads. A red check, on the pull request that caused it, while the author still has the context to fix it. A regression caught three weeks later belongs to nobody.

Overriding it is one line, and it leaves a trace. Sometimes shipping the heavy dependency is the right call. If the only way past the gate is to disable it globally, someone will disable it globally. Give them a documented per-PR override that shows up in the diff, so the decision is visible in review rather than made in silence.

Measure the thing users experience

Lab numbers in CI catch regressions. They do not tell you what your users actually get, because your users are not a CI runner in Frankfurt.

If you have the traffic, collect field data — the real Core Web Vitals from real sessions — and look at the 75th percentile, not the average. The average is dominated by your own team on office wifi. The 75th percentile is a person on a three-year-old phone on mobile data, which is who the budget exists for.

The unglamorous wins, in order

Most sites do not need clever work. In rough order of payoff per hour:

  • Delete something. The fastest script is the one you removed. Start with third-party tags — audit what each one is for and who asked for it.
  • Stop blocking the render. Self-host fonts, preload the two faces the first screen actually uses, and get everything else off the critical path.
  • Ship less JavaScript, not faster JavaScript. Server-render what does not need interactivity. A component that never responds to a click has no business hydrating.
  • Size and format your images properly, and give them explicit dimensions so nothing shifts while they load.
  • Only then reach for code-splitting cleverness.

The part that makes it stick

Put the current numbers somewhere the whole team sees weekly — the same place you show error rates. Performance becomes a shared property of the codebase when it is visible, and an occasional crisis when it is not.

None of this is difficult. It is just easier to schedule an audit than to enforce a threshold, and the audit is the option that does not work.


Written by the Thrivee team in Novi Sad. If something here maps onto a problem you are carrying, tell us about it .

Next step

Tell us what has to ship, and by when.

One call, no deck. If we are not the right team for it we will say so, and usually point you at who is.

Start a conversation branislav@thrivee.io

Typical reply within one business day · CET / CEST (UTC+1 / UTC+2)