⚡ Web performance

Performance budgets: stopping regressions before release

How to set weight and timing limits for a site, check them automatically on every change with Lighthouse CI and deal with the variability of measurements.

Making a site fast is a project; keeping it fast is a habit. Without systematic checks, performance degrades little by little: one more library, an unoptimized image, a new marketing script. No single change seems serious, but after a few months the optimization work has been undone. A performance budget is a set of explicit limits the site must not exceed, checked automatically on every change.

What kind of limits to set

Budgets fall into three families, with different strengths:

Budget type Examples Strengths Weaknesses
Quantity KB of JavaScript, number of requests, image weight Stable and easy to measure Indirect relative to the experience
Timing LCP, TBT, lab time to interactive Close to what the user perceives Variable between runs
Scores Lighthouse performance score Concise and understandable Hide the cause

An effective combination uses quantity budgets as the first filter, because they are deterministic and an increase in weight shows up immediately, and timing budgets as a safety check on the main pages.

How to choose the values

Values should not come from a generic table but from the site itself:

  1. measure the current state of the most important pages, such as the home page, a product page and an article;
  2. if they are already good, set the budget slightly above current values to block any regression;
  3. if they are poor, set the budget at current values and lower it gradually as optimizations progress;
  4. for timings, start from the Core Web Vitals thresholds on field data and derive a stricter lab target, because the lab does not represent the worst devices.

Lighthouse CI

Lighthouse CI is the most widely used tool for this purpose. It plugs into the continuous integration pipeline, runs Lighthouse on the listed pages for every change and fails if the assertions are not met. A minimal configuration:

{
  "ci": {
    "collect": {
      "url": ["http://localhost:8080/", "http://localhost:8080/product/example"],
      "numberOfRuns": 3
    },
    "assert": {
      "assertions": {
        "largest-contentful-paint": ["error", { "maxNumericValue": 2500 }],
        "total-blocking-time": ["warn", { "maxNumericValue": 200 }],
        "resource-summary:script:size": ["error", { "maxNumericValue": 250000 }]
      }
    }
  }
}

Assertions can be at error level, which blocks the change, or warn, which reports without blocking. It is best to start with many warnings and few blocks, then tighten the rules that prove reliable.

The variability problem

Timing measurements vary from run to run, even with no code changes at all. Pipeline machines are shared, load changes, the simulated network is not perfectly constant. A timing budget that is too tight produces false alarms, and a check that raises false alarms is soon ignored or switched off.

Some practical countermeasures:

  • multiple runs: with three or five measurements per page, Lighthouse CI considers a representative value rather than a single lucky or unlucky one;
  • realistic margins: leave room between the typical value and the limit;
  • quantity budgets for blocking: bytes of JavaScript do not fluctuate, so they are ideal for error-level assertions;
  • a stable environment: where possible, use dedicated or fixed-size machines for measurements.

Size limits on code

For JavaScript-heavy projects there are lighter tools that check only the weight of the output files. size-limit, for example, reads a configuration in package.json and fails if a file exceeds the limit, also showing which dependency grew:

"size-limit": [
  { "path": "dist/app.js", "limit": "120 KB" },
  { "path": "dist/vendor.js", "limit": "90 KB" }
]

The check is fast, deterministic and runs in seconds, so it can also run locally before every commit.

Making the result visible

A budget really works when whoever changes the code sees the effect immediately. Lighthouse CI can publish reports and compare them with those of the main branch; many platforms let you automatically comment on the pull request with differences in weight and timing. A line such as "JavaScript: +38 KB compared with the main branch" starts a discussion before the problem reaches users.

When a limit is exceeded there are three legitimate responses: optimize the change, give up something else to compensate, or consciously decide to raise the budget and document why. The one to avoid is switching off the check.

Don't forget field data

Lab budgets prevent obvious regressions, but they do not see everything: content loaded from the CMS, scripts added through a tag manager or problems tied to specific devices escape the pipeline. That is why they should be paired with field data monitoring, with alerts when the 75th percentile of a metric worsens consistently.

In short

  • A performance budget sets explicit limits and checks them on every change.
  • Combine stable quantity budgets with timing budgets on the main pages.
  • Derive values from the site's current state and lower them gradually.
  • Handle variability with multiple runs, realistic margins and stable environments.
  • Make differences visible in pull requests and pair the budgets with field data.
MM
Marco Marzaduri

Marco Marzaduri works in cybersecurity, digital forensics and web development. A computer science graduate, he combines rigorous technical analysis with attention to the people who use technology.

Read also