The Hasan Dispatch logo

The Hasan Dispatch

Archives
Log in
Subscribe
June 8, 2021

Performance Budgets: A Love Letter

Performance budgets are the only performance optimization that actually works long-term. Here's why, and how to set them up.

#Performance #Web Vitals #Architecture

The Problem with One-Time Optimization

Most performance work follows the same pattern: someone notices the application is slow, a task is created to "improve performance," an engineer spends a sprint optimizing bundle size and lazy-loading components, the metrics improve, and everyone moves on. Three months later, the application is slow again, because the optimization was a one-time effort and the codebase has been accumulating new features, new dependencies, and new unoptimized code in the interim. Performance work done this way is like cleaning a room — it looks great immediately after, but it gets messy again unless you change the habits that made it messy in the first place.

Performance budgets are the habit change. Instead of treating performance as a one-time optimization, a performance budget treats it as a continuous constraint: every route has a maximum bundle size, every page has a maximum load time, and any change that exceeds the budget has to either justify the increase or refactor to stay within it. The budget is enforced mechanically — in CI, automatically, on every pull request — so it doesn't depend on anyone remembering to check. The mechanical enforcement is the entire point, because performance regression is a slow, invisible decay that no one notices in the moment and everyone notices when it's too late to easily fix.

Setting Up a Budget

The first step in setting up a performance budget is measuring the current state. You can't set a budget without knowing where you are, and the measurement needs to be automated so it's repeatable. We use Lighthouse CI to measure Core Web Vitals (LCP, FID, CLS) and bundle size on every pull request, and the results are posted as a comment on the PR. That visibility alone — making performance visible on every change — is valuable even before you set any budgets, because it makes performance a part of the conversation rather than something that's only discussed when it becomes a problem.

Setting the budget levels is a judgment call. Too tight, and every PR becomes a fight over the budget; too loose, and the budget doesn't actually prevent regression. We started by measuring the current values and setting the budget at 110% of the current state — tight enough to catch significant regressions, loose enough to allow normal feature work. Over time, as the team got better at writing performant code, we tightened the budgets incrementally, ratcheting them down by 5-10% each quarter. That gradual tightening meant the budget was always a stretch but never a crisis, and it created a steady pressure toward better performance without requiring dramatic interventions.

Why It Works

Performance budgets work because they change the default. Without a budget, the default is "add whatever you need and optimize later," and later never comes because there's always a more urgent feature to build. With a budget, the default is "stay within the budget or explain why you can't," and that default forces performance to be considered at the time of implementation rather than deferred. The deferral is what kills performance in the long run, because code that was written without performance in mind is much harder to optimize after the fact than code that was written with performance in mind from the start.

The other reason budgets work is that they make performance visible to the business. A budget violation on a PR is a concrete, quantifiable thing — "this change adds 50KB to the initial bundle, which exceeds the budget by 15%" — that can be discussed in terms of trade-offs rather than vibes. "The application feels slow" is subjective and easy to dismiss; "the budget has been exceeded by 15% on the last three PRs" is objective and requires a response. That objectivity is what gives engineers the leverage to push back on changes that would harm performance, and it's what makes performance a shared responsibility rather than an engineering-only concern.

Don't miss what's next. Subscribe to The Hasan Dispatch:
Share this email:
Share on LinkedIn
GitHub
🙃
LinkedIn
Powered by Buttondown, the easiest way to start and grow your newsletter.