Skip to main content
Engineering7 min readSeptember 15, 2025

Performance Budgets: Keeping Web Apps Fast

How to set and enforce performance budgets for web applications. Practical approaches to preventing performance regression as your application grows in complexity.

James Ross Jr.
James Ross Jr.

Strategic Systems Architect & Enterprise Software Developer

Performance Doesn't Degrade in Big Jumps

No one decides to make their web application slow. Performance degrades incrementally — a new analytics library here, a larger hero image there, an unoptimized component that re-renders on every keystroke. Each addition is small enough to seem harmless. But after six months of small additions, the application that loaded in 1.2 seconds now loads in 4.8 seconds, and nobody can point to a single change that caused it.

Performance budgets prevent this death by a thousand cuts. A performance budget is a set of quantitative limits — on page weight, on load time, on JavaScript bundle size, on specific web performance metrics — that the team agrees to enforce. When a change would push the application past its budget, the team must either optimize the change, remove something else to make room, or make a conscious decision to revise the budget.

The budget transforms performance from an afterthought into a first-class constraint, similar to a financial budget. You don't add expenses without checking whether you can afford them. Performance budgets apply the same discipline to your application's resource consumption.


Setting Meaningful Budgets

The budgets that matter are the ones tied to user experience outcomes, not arbitrary technical thresholds.

Largest Contentful Paint (LCP) should be under 2.5 seconds. This measures when the main content of the page becomes visible to the user. It's the metric most closely associated with perceived load speed and the one Google uses as a Core Web Vital for ranking purposes. If your LCP is above 2.5 seconds on a representative connection, you have a problem that users feel on every page load.

Total JavaScript bundle size is the budget most directly within developer control. Every kilobyte of JavaScript must be downloaded, parsed, compiled, and executed before the application becomes interactive. I set aggressive budgets here — typically 200KB compressed for the initial load, with code splitting ensuring that route-specific code is loaded on demand. This forces deliberate decisions about which libraries to include and incentivizes lighter alternatives.

Total page weight including images, fonts, styles, and scripts should stay under a defined threshold. For most applications, I target 1MB for the initial page load. Images are usually the biggest contributor, and they're also the easiest to optimize — proper formats (WebP, AVIF), responsive sizing, and lazy loading can reduce image weight by 60-80% without visible quality loss.

Time to Interactive (TTI) measures when the page is not just visible but usable — when the user can click buttons, fill forms, and navigate without lag. A page that renders in two seconds but doesn't respond to input for another three seconds provides a terrible user experience. Budget TTI at no more than one second after LCP.

These budgets should be based on your actual users' conditions. Use your analytics to understand the devices and connections your users have. Setting budgets based on your development machine's performance and your office's fiber connection is meaningless if your users are on mid-range phones over LTE. The Core Web Vitals optimization work I've done consistently shows that real-user metrics differ dramatically from lab metrics.


Enforcing Budgets in Your Workflow

A performance budget that isn't enforced is a suggestion. Enforcement means integrating budget checks into your development workflow so that budget violations are caught before they reach production.

Build-time bundle analysis is the first line of defense. Tools like webpack-bundle-analyzer, rollup-plugin-visualizer, or Nuxt's built-in bundle analysis show the size impact of every dependency and every code-split chunk. Run this as part of your build process and fail the build if bundle sizes exceed the budget. This catches the most common source of budget violations — adding a large dependency without realizing its size impact.

CI performance testing using Lighthouse CI or similar tools runs performance audits on every pull request and compares the results against your budget thresholds. When a PR introduces a performance regression, it shows up in the PR checks before code review, just like a failing test. This makes performance a team concern rather than a periodic audit finding.

Real-user monitoring (RUM) measures actual performance in production. Lab tests and CI checks are necessary but insufficient because they can't replicate the full diversity of user conditions. RUM data shows your actual LCP, TTI, and other metrics across all users, all devices, all connections. When RUM data shows budget violations that lab testing missed, you've found a gap in your testing approach.

Set up alerts on RUM thresholds. When your p75 LCP crosses your budget threshold, the team should know immediately — not during a quarterly review. Performance issues that go unnoticed for weeks become entrenched as new code is built on top of the slow foundation.


When the Budget Is Exceeded

Budget violations are not failures — they're decision points. When a change would push the application past its budget, the team has three options.

Optimize the change to fit within the existing budget. Can the new library be replaced with a lighter alternative? Can the component be lazy-loaded instead of included in the initial bundle? Can the image be further compressed? This is the most common response and the one that drives continuous improvement.

Make room by optimizing something else. The budget is a total constraint, and improvements in one area create capacity for additions in another. This incentivizes regular performance maintenance — cleaning up unused CSS, removing deprecated dependencies, optimizing queries — because it directly enables new features.

Revise the budget with a clear justification. Sometimes a feature genuinely requires more resources than the current budget allows, and the business value justifies the trade-off. This is a valid decision when made consciously. The budget revision should be documented with the reasoning, similar to how you'd document any architectural decision with significant trade-offs.

What you should never do is ignore the violation and promise to fix it later. "We'll optimize next sprint" becomes "we'll optimize after launch" becomes "we'll optimize when someone complains." Performance optimization that isn't built into the regular development workflow rarely happens at all. Budgets work because they make the trade-off explicit at the moment the decision is being made, not after the fact.