Tags: web-dev concept

Performance Budgets

Date: 2026-08-16


A threshold that fails a build. Anything softer is a dashboard nobody reads — performance decays by default, one reasonable addition at a time, and only an automatic gate reverses that.


What it is

A performance budget is a quantified limit on some property of a page, enforced automatically, where exceeding it blocks a change from shipping.

The enforcement is the definition. A target that produces a warning is a target; a target that fails CI is a budget.

Why decay is the default

No single change makes a site slow. Each addition is individually reasonable — a review widget, a personalisation script, a slightly larger hero, another font weight — and each is approved by someone who isn’t looking at the cumulative number.

month 1   LCP 1.9s   ✓
month 4   LCP 2.2s   ✓  "still passing"
month 8   LCP 2.6s   ✗  and nobody can say which change did it

In plain terms: performance is a commons. Everyone’s contribution is small, nobody’s is decisive, and without a gate the total only goes one way.

What to budget

Three kinds, and you want at least one from each.

KindExamplesBest for
QuantityJS bytes, image weight, request count, third-party originsEasy to measure, deterministic, catches regressions early
TimingLCP, INP, CLS, TTFB, Total Blocking TimeClosest to experience, noisier in CI
RuleNo render-blocking scripts; no unfingerprinted assets; no new third-party origin without reviewBinary, unambiguous, easy to enforce

Quantity budgets are the most practical to gate on because they’re stable run to run. Timing budgets in a lab are noisy — use a median of several runs and a generous threshold, or gate on quantity and monitor timing in the field.

Setting the numbers

Not aspirationally. Three defensible sources:

  • Your current p75, minus a little. Prevents regression, which is most of the value
  • The Core Web Vitals thresholds — LCP 2.5s, INP 200ms, CLS 0.1 at p75 — as the outer bound
  • Faster than your competitors on the same templates, if the argument needs an external anchor

A starting set for a retail site:

JS (initial, compressed)        < 170 KB
images (above the fold)         < 300 KB
fonts (total)                   <  60 KB
third-party origins             <  10
requests before first paint     <  20
LCP  (lab, throttled 4× CPU)    < 2.5s
Total Blocking Time             < 300ms
CLS                             < 0.05

Budget per template, not site-wide. A product page and a category page have different content and different budgets, and one site-wide number either strangles the heavy template or lets the light one drift.

Enforcing it

1  CI runs a lab measurement on key templates, fixed config, CPU throttled
2  compare against the budget file, committed to the repo
3  exceed it → the build fails, with the offending metric named
4  raising the budget requires a pull request someone reviews

That last point is what makes it work. The budget will need raising sometimes — a genuinely valuable feature costs bytes — and the point isn’t to prevent that. It’s to make the cost visible and the decision explicit rather than absorbed silently.

Lighthouse CI, bundle-size checkers and most CDNs’ CI integrations all support this; the mechanism matters less than the failure being blocking.

Pairing it with the field

Lab budgets catch regressions before they ship. They don’t tell you whether real users improved.

  • Gate in CI on lab and quantity budgets — deterministic, fast, blocking
  • Monitor in the field on Core Web Vitals — real devices, real connections, and the number that actually counts — Real User Monitoring, Field vs Lab Data
  • Alert on field regression with a longer window, because field data is noisy and lagged — Alerting on Metrics

Failure modes

  • Warnings instead of failures. Ignored within a fortnight
  • One site-wide budget across templates with different content
  • Set so loosely nothing ever fails — a budget that has never blocked anything isn’t a budget
  • Set so tightly everything fails, so the team routes around it or disables it
  • No route to raise it. People bypass a gate they can’t legitimately open
  • Third parties exempted, which exempts the largest contributor — Third-Party Scripts, Tag Manager Performance
  • Nobody owns it. A failing build with no owner gets skipped. See Performance Culture