Tutorial · performance · Published 2026-08-16 · 3 min read

Setting and enforcing performance budgets

Performance budgets: set byte weight, timing and Core Web Vitals limits, then enforce them in Lighthouse CI so performance regressions fail the build.

What to set a budget on

A performance budget is a limit placed on a performance-related quantity or metric so that work stays within an agreed ceiling. Without a budget, performance is a preference; with one, it becomes a constraint checked at build time. Budgets usefully split into three layers that reinforce each other:

The field layer tells you whether the outcome is good; the lab and quantity layers are the early-warning signals that catch a regression before it ever reaches the field. The field vs lab article explains why you need both; this article focuses on the budget mechanics.

Choosing default numbers

Industry and vendor guidance gives sensible starting points, but you should derive the authoritative values from your own routes and devices, not adopt them blindly. Common baselines include: JavaScript under roughly 170 KB compressed on the initial load, CSS under roughly 50 KB, total page weight under about 1 MB on mobile for a content site, images under roughly 500 KB of payload per page, and a Core Web Vitals bar of LCP at or under 2.5 seconds, INP at or under 200 ms, and CLS at or under 0.1 at the 75th percentile. In the lab, a common desk target is Total Blocking Time under 200 ms, which correlates with a good INP in the field.

Whatever numbers you pick, budget each important route separately (a checkout page is not a blog page) and tighten toward existing winners rather than global one-size rules. A proven rhythm is the ratchet: when a route comfortably exceeds its target, set the next budget about 10% tighter and re-ship.

Enforcing in Lighthouse CI

Budgets only matter if something stops a regression from landing, and Lighthouse CI makes the assertion easy. The budget.json file in the report's Collect options controls the audible per-resource size budgets (in kilobytes), while the CI assert block handles the per-audit thresholds. Note the unit difference: resource-size budgets use kilobytes, but the numeric assertion equivalents use bytes. A minimal lighthouserc.json looks like this:

{
  "ci": {
    "collect": {
      "url": ["https://example-stage.example/"],
      "numberOfRuns": 3
    },
    "assert": {
      "assertions": {
        "categories:performance": ["error", { "minScore": 0.9 }],
        "largest-contentful-paint": ["error", { "maxNumericValue": 2500 }],
        "interaction-to-next-paint": ["error", { "maxNumericValue": 200 }],
        "total-byte-weight": ["error", { "maxNumericValue": 1048576 }]
      }
    }
  }
}

Wiring this into the pipeline is described fully in the Lighthouse CI article. Set warnings at roughly 85 to 90% of the ceiling and errors at the ceiling itself, so the team gets early smoke alarms rather than only hard stops. Finally, remember that a lab budget passing says nothing about the field: pair the CI gate with a field-data monitor so a third-party or CDN regression you will not catch in a single synthetic run still surfaces in CrUX over time.

Need a website built, fixed, optimised, migrated or replaced?

This technical resource is written by CSMBAC, a small design and development studio. If you would rather hand the problem to a professional, the website service page explains how we build enquiry-ready websites.

Explore website services