The budget has to be derived from your own site’s real field data, not borrowed from a generic industry number, because the relationship between resource weight (JS bytes, image bytes, third-party request count) and actual Core Web Vitals pass/fail rates depends on your specific traffic mix, device tiers, and network conditions. The construction process is: correlate your own historical RUM/CrUX data against your own field pass/fail rates to find the thresholds that matter for your traffic, set budgets at those thresholds, and enforce them in CI so regressions get caught before they reach production and degrade the field metric.
Why generic resource-weight budgets don’t predict field pass rates
Core Web Vitals field pass/fail status is a function of real user conditions interacting with your resource weight, not resource weight in isolation. A JS payload that causes INP failures on a site whose traffic is 70% lower-end Android devices on constrained networks might barely register as a problem on a site whose traffic mostly comes from high-end devices on fast connections. This is why a fixed rule like “200KB of JS” or “500KB total page weight” borrowed from a blog post or conference talk, without reference to your own traffic’s device/network profile, will be either too lax (still allowing field failures) or unnecessarily strict (constraining development for no real field benefit) depending on how your actual users differ from whatever assumption produced that generic number.
The correlation has to run in the direction of: “for our site, what resource-loading thresholds actually predict field pass/fail,” derived from looking at your own historical data across pages/templates with varying resource weights and their corresponding field Core Web Vitals outcomes. This is a genuinely site-specific empirical exercise, not a lookup.
It also matters which metric you’re correlating resource weight against, since JS bytes, image bytes, and third-party request count don’t map onto the three Core Web Vitals uniformly. JS parse/compile/execution cost is the dominant lever for INP, since long tasks running on the main thread are what delay the browser’s response to user input; image weight is the dominant lever for LCP on templates where the largest contentful element is an image; and layout-shifting resources (web fonts without font-display fallback sizing, ads or embeds injected without reserved space, images without dimensions) drive CLS somewhat independently of raw byte weight. A single combined “total page weight” budget can obscure this, since a page could pass a total-weight budget while still carrying a JS profile heavy enough to fail INP, or vice versa. Correlating and budgeting per metric, rather than against one aggregate weight number, produces a budget structure that actually maps to the failure mode you’re trying to prevent.
How to derive and enforce a field-data-based performance budget
Pull historical RUM or CrUX data segmented by template or URL group, alongside the corresponding resource metrics for those same templates (total JS bytes, total image bytes, number of third-party requests/domains, font-loading strategy). If you don’t have historical RUM correlating resource weight with per-page CWV outcomes, start capturing both together going forward, since you can’t correlate what you haven’t measured jointly.
Identify the threshold pattern in your own data: at what JS byte range does field INP pass rate start dropping meaningfully for your traffic mix? At what image byte weight does field LCP start failing at p75 for your device/network distribution? This is where the actual budget numbers come from, they’re an empirical finding specific to your site, not a constant you can copy from elsewhere.
Set budgets at the resource level based on that finding (a JS budget in KB, an image weight budget per template, a third-party request count/domain limit), and document explicitly that these numbers were derived from your own field-data correlation, so future revisions to the budget are done by re-running the correlation, not by guessing a new number.
Enforce budgets in CI using tooling like Lighthouse CI’s budgets.json configuration, which can fail a build or flag a warning when a page exceeds the configured resource thresholds before it ever reaches production, closing the loop between the budget you derived from field data and the day-to-day development process that would otherwise silently erode it.
Segment the budget by template, not just site-wide. A single global budget applied uniformly across a product listing page, a blog post template, and a homepage tends to be wrong for all three, since each carries a different baseline resource profile and a different mix of third-party dependencies. Deriving separate thresholds per template (using the same field-data correlation method, just segmented by template/URL group instead of aggregated across the whole site) produces budgets that are meaningfully enforceable rather than either too loose on heavy templates or needlessly restrictive on lightweight ones.
Distinguish lab-measured CI checks from the field data the budget was derived from. Lighthouse CI runs under fixed, synthetic network and CPU throttling profiles, which is useful for catching regressions consistently in a controlled environment, but a page that passes its budget in CI under lab throttling can still fail in the field if your actual traffic’s device and network mix is worse than the lab profile assumes. Treat CI budget enforcement as a regression gate that operates on lab proxies for the resource thresholds you derived from field data, and periodically confirm the two are still in agreement by checking whether pages passing CI budgets are, in fact, showing improved or stable field pass rates in CrUX; if they diverge, the lab throttling profile or the derived thresholds need recalibration, not the field data itself.
Account for HTTP/2 versus HTTP/3 and connection reuse when interpreting request-count budgets specifically. A third-party request count budget derived under an HTTP/1.1 or early HTTP/2 assumption, where each additional request carried meaningful connection overhead, may be overly conservative on infrastructure running HTTP/3 with better multiplexing and lower per-request overhead. Request count still matters (each third-party script still carries parse/execution cost independent of transport), but don’t assume the historical rationale for a request-count ceiling still applies unchanged if the underlying protocol stack has changed since the budget was first derived.
Re-validate periodically. Traffic mix, average device capability, and third-party script load all shift over time, so the correlation between resource weight and field pass rate isn’t static; treat the budget as something to re-derive periodically against fresh field data rather than a number set once and never revisited.
Avoid citing or trusting any claim of a universal “X KB of JS equals Y% pass rate” relationship, since no such fixed industry constant exists or could exist given how much the relationship depends on a given site’s specific real-world traffic conditions.
A worked example of deriving a site-specific threshold
Suppose a mid-size SaaS site pulls a year of RUM data segmented by template and finds that its product-listing template pages with total JS under roughly 350KB show a field INP pass rate around 92%, while pages on the same template carrying 600KB or more of JS see that pass rate drop to around 61%, with the decline concentrated specifically among sessions on lower-end Android devices. That 350KB-ish range, derived from this site’s own traffic mix and device distribution, becomes the JS budget for that template, enforced in CI going forward. A different hypothetical site with a traffic mix skewed toward high-end desktop devices on fast connections might run the same correlation and find its INP pass rate doesn’t meaningfully degrade until JS weight crosses 900KB, because its device and network profile can absorb more script execution before main-thread delay becomes noticeable. Neither number is transferable to the other site; each is only valid because it was derived from that site’s own field data against its own traffic.