Skip to content

Performance budgets belong in CI, not in a retrospective

A Lighthouse score is a snapshot. A budget that fails the build is a constraint. Here is the setup we ship with every front-end.

Sofia Lindqvist

Sofia Lindqvist

Staff Frontend Engineer

5 min read
Share

Every team has run a Lighthouse audit, celebrated a 96, and watched it drift to 71 over the following quarter. The audit was not wrong. It was just a photograph of a moving thing.

Performance is not a score you achieve. It is a constraint you enforce, and the only place a constraint holds is in the pipeline.

Budget the bytes, not the score

Lighthouse scores are noisy — they move with machine load, network conditions and the phase of the moon. Failing a build on a composite score produces flaky CI and, within two weeks, a team that skips the check.

Budget the things that are deterministic:

{
  "budgets": [
    {
      "path": "/*",
      "resourceSizes": [
        { "resourceType": "script", "budget": 180 },
        { "resourceType": "stylesheet", "budget": 60 },
        { "resourceType": "font", "budget": 120 },
        { "resourceType": "total", "budget": 500 }
      ]
    }
  ]
}

Bundle size is stable across runs. If the JavaScript budget goes from 178KB to 214KB, something specific happened in this pull request, and the diff is right there.

Keep the Lighthouse run, but report it rather than gate on it. Gate on bytes.

Attribute the regression in the PR

A build that fails with "bundle too large" sends someone on a twenty-minute hunt. A build that fails with "+34KBdate-fns added to the client bundle" is fixed in two minutes.

Emit a comparison against the base branch as a PR comment: per-chunk deltas, and the new dependencies responsible. The point is to make the cheapest moment to fix a regression also the most obvious one.

The four things that actually regress

Across the front-ends we have built, budget failures cluster:

A date or utility library reaching the client. Almost always importable server-side only, or replaceable with Intl. Intl.DateTimeFormat covers the majority of what teams reach for date-fns to do.

An icon set imported wholesale. import * as Icons pulls in thousands. Bundle the icons you use explicitly — and remember that dynamically named icons (:name="item.icon") are invisible to static scanners, so they need listing by hand.

Fonts. Self-host, subset to the ranges you serve, preload only the weights used above the fold. Four weights of two families at full Latin coverage is 300KB nobody asked for.

Images without dimensions. Not a byte problem, a layout-shift problem, and the one most likely to make a fast site feel slow. Width and height on every image, always.

Measure the field, not the lab

Lab metrics tell you whether a change helped. Field metrics tell you whether your users are having a good time. They disagree more often than people expect, because your laptop is not a mid-range Android on a train.

Ship web-vitals, send p75 to your analytics, and put LCP, INP and CLS on a dashboard someone looks at weekly. When lab and field diverge, the field is right.

Reasonable starting numbers

For a content-led marketing site, these are achievable without heroics:

MetricTarget (p75, field)
LCP< 1.5s
INP< 150ms
CLS< 0.05
JS transferred< 180KB
Total transferred< 500KB

An application will need looser numbers. Set them deliberately rather than inheriting them, and write down why.

The part that is cultural

The technical setup takes an afternoon. Keeping it takes agreement that a budget failure blocks a merge like a failing test does.

Without that, the check becomes advisory, advisory becomes ignored, and you are back to running an audit every quarter and wondering where the score went.

  • #Performance
  • #CI
  • #Nuxt
Share

Related reading

Engineering6 min read

Your component API is a contract, so write it down

Variant tables, prop naming and the boolean that should have been an enum. Notes from maintaining component libraries other people have to use.

Sofia LindqvistSofia Lindqvist
Design Systems7 min read

Design tokens that survive a rebrand

Most token systems break the first time the brand changes. The fix is a layer most teams skip — and it costs about a day to add.

Elena MarshElena Marsh
AI8 min read

Designing for models that are wrong

Every AI feature has a failure rate. Most interfaces are designed as though it were zero, and users learn to distrust the whole product.

Kwame BoatengKwame Boateng

One useful email a month

Design system patterns, front-end techniques and case study breakdowns. No promotions, no digest of other people's links.

Unsubscribe anytime. We never share your address.