Performance work tends to happen in bursts — a team notices the site got slow, spends a sprint optimizing, ships it, and then performance quietly degrades again over the following months as new features and dependencies get added without anyone tracking the cumulative cost. Performance budgets exist specifically to break that cycle by turning "keep the site fast" from an occasional cleanup project into an enforced constraint on every change.
What a performance budget actually is
A performance budget sets concrete numeric limits on the things that make a page slow — bundle size, image weight, number of third-party scripts, and the Core Web Vitals metrics themselves — and treats crossing those limits as a build failure, not a suggestion. Representative starting-point budgets that show up consistently across current guidance: HTML under 50KB, CSS under 60KB, JavaScript under 150KB gzipped, above-the-fold images under 200KB, fonts under 80KB, and total initial page weight under roughly 540KB for mobile. These aren't universal rules — the right numbers depend on your specific product and audience — but they're a reasonable starting point if you don't already have budgets defined.
On the Core Web Vitals side, the budget mirrors Google's own thresholds: LCP under 2.5 seconds, INP under 200ms, CLS under 0.1. Setting your CI budget at or slightly below these public thresholds gives you a buffer before a regression actually shows up as a ranking or user-experience problem.
Why enforcement in CI is the part that actually matters
A performance budget that lives in a document somewhere gets ignored the same way most policies not backed by tooling get ignored — a deadline pressure, a "we'll fix it later," and the budget quietly stops being enforced. The practical fix is wiring the budget into your CI pipeline directly: Lighthouse CI is the most common tool for this, running a Lighthouse audit on every pull request and failing the build if scores drop below defined thresholds. This turns a performance regression into the same category of problem as a failing test — something that blocks a merge rather than something that gets noticed weeks later in a dashboard nobody's watching.
What to actually budget for
Beyond the headline Core Web Vitals numbers, the more granular budgets that catch regressions earlier:
- JavaScript bundle size, since this is usually the biggest lever on both load time and interaction responsiveness (INP) — a bundle that creeps up gradually as dependencies get added is one of the most common silent regressions.
- Third-party script count and weight — analytics tags, chat widgets, ad scripts, and similar third-party additions are a classic source of performance creep, since each one gets added independently without anyone evaluating the cumulative cost.
- Image weight, particularly above-the-fold images that directly affect LCP — a single unoptimized hero image can single-handedly blow a page's LCP budget.
Lighthouse CI alone isn't enough — the lab-vs-field gap
A common failure mode for teams that adopt performance budgets is treating a passing Lighthouse CI check as proof the site is actually fast for real users, when it only proves the site is fast under simulated lab conditions. Synthetic testing (what Lighthouse CI runs) simulates traffic in a controlled environment before code ships, while Real User Monitoring (RUM) measures actual performance from real visitors' devices and networks in production — and these two measurements diverge more than most teams expect. Analysis of real-world CrUX data has found that a significant share of pages scoring well in Lighthouse still fail one or more Core Web Vitals thresholds under real-world conditions, because lab tests run on a fixed simulated network and device profile that doesn't capture the full spread of actual user conditions — slower phones, throttled mobile connections, ad blockers, background tab behavior, and similar real-world variance that synthetic tests can't replicate.
The practical fix is running both, with each catching a different class of problem: synthetic testing in CI catches regressions before they reach production and pinpoints whether an issue is global or code-related, while RUM validates that the CI-approved build actually performs well for the real, messy distribution of users and devices hitting it in production. A clean pattern that shows up consistently in current guidance is "synthetic before release, RUM after release" — Lighthouse CI as the pre-merge gate, and a RUM tool tracking the same Core Web Vitals metrics in production as the ongoing check that the budget is holding up outside the lab. A performance budget program that only has the synthetic half is missing the half that actually confirms the budget is working.
What actually blows the INP budget, and how to fix it
INP is worth singling out for a budget-specific breakdown because it remains the Core Web Vital most teams miss in practice — Chrome UX Report data shows roughly 40% of origins on mobile still fail to meet INP thresholds even in 2026, making it a persistent, widespread liability rather than an edge case. The mechanism is a main-thread blocking problem: any task that blocks the main thread for more than 50ms counts as a "long task," and a task actively running when a user interacts with the page delays the browser's ability to respond at all. For a genuinely good INP score, individual tasks should stay under roughly 15ms — tight enough that a single unoptimized event handler or a heavy third-party script easily blows the budget on its own.
Three concrete techniques address most INP long-task problems: breaking up long JavaScript tasks with scheduler.yield() (falling back to setTimeout where unsupported) so the browser can interleave user input handling between chunks of work; deferring non-visual work to requestIdleCallback or a Web Worker so it doesn't compete with interaction handling on the main thread; and applying the CSS contain property to independent page sections, which tells the browser a change in that section won't affect layout elsewhere and measurably speeds up the "next paint" phase that follows an event handler. For diagnosing which specific script or task is the culprit, the Long Animation Frames (LoAF) API is described as the current gold-standard tool — it identifies exactly which script caused a given frame to exceed budget, which is far more actionable in a CI failure than a raw INP number alone. Adding a LoAF-based check, or at minimum a long-task budget threshold, to the same Lighthouse CI pipeline enforcing bundle size gives a team a much more direct signal for the metric that's hardest to hit.
The practical takeaway
If your team doesn't already have enforced performance budgets, the fastest path to real impact is adding Lighthouse CI (or an equivalent) to your pull request pipeline with a handful of concrete thresholds — even rough starting numbers, tightened over time — rather than waiting to define the perfect budget before enforcing anything. A budget that's enforced imperfectly catches far more regressions than a well-designed budget that nobody checks.
Sources: dev.to/nxfold_9a37c0ceb3a755db04, digitalapplied.com, codeminer.co, debugbear.com, atatus.com, sitepoint.com, debugbear.com/inp
Keep reading
Get new posts as they publish
No spam — just the next post, straight to your inbox.