A website rarely becomes slow because of one obviously bad decision.
It becomes slow through reasonable additions.
A larger hero image. Another font weight. A chat widget. A new analytics script. A library added for one component. Each change looks acceptable in isolation. Together, they create a page that takes longer to load, reacts more slowly, and shifts while someone is trying to use it.
A web performance budget turns speed from a final audit into a constraint that can be checked during development.
The budget defines how much a page is allowed to cost. That cost can include transferred JavaScript, image weight, request count, loading time, or another measurable limit. When a change exceeds the limit, the team has to make a decision before the regression reaches production.
That is the useful part. A performance budget does not automatically make a site fast. It makes performance loss visible while there is still time to fix it.
A Lighthouse score is not a budget
Running PageSpeed Insights after launch can reveal problems. It does not prevent them.
A Lighthouse score is also a calculated result, not a permanent property of a website. Test conditions, hardware, network simulation and page content can affect the result. The Lighthouse CI documentation explicitly warns that browser measurements contain variance and recommends asserting stable facts, such as the size and number of JavaScript requests, before relying too heavily on more variable conclusions.
A budget is different because it states the acceptable limit before the test runs.
For example:
- Total transferred JavaScript must stay below 180 KB.
- The page must make no more than five third-party requests during the measured load.
- The total resources observed during that load must stay below 900 KB.
- No new production route may be merged without a repeatable performance check.
These figures are examples, not universal recommendations. A product dashboard, editorial site and image-heavy portfolio have different requirements.
The value comes from choosing limits that reflect the product and then enforcing them consistently.
Budget what the team can control
A practical performance budget normally combines three types of limits.
1. Resource size
Resource budgets limit the amount of data transferred for categories such as JavaScript, CSS, images, fonts or the whole page.
This is usually the easiest place to start because the cause of failure is concrete. If JavaScript exceeds its budget, the team can inspect a dependency, remove unused code, split a bundle or defer work that is not required for the initial view.
Compressed transfer size matters because that is what travels across the network. Parse and execution costs still matter too, especially for JavaScript, so a small transferred file should not be treated as proof that runtime performance is acceptable.
2. Resource count
Request budgets limit how many files or third-party resources the page loads.
The number of requests is not automatically a problem under every protocol and connection. The useful question is what those requests represent.
Ten requests to a controlled origin are different from ten third-party scripts with separate connections, execution costs and unpredictable response times. A request limit can expose a growing collection of trackers, embeds and services before they become an architectural dependency.
3. User-facing metrics
Metric budgets cover outcomes such as loading, responsiveness and visual stability.
The current Core Web Vitals are:
| Metric | What it represents | Google's good threshold |
|---|---|---|
| Largest Contentful Paint (LCP) | Loading performance | 2.5 seconds or less |
| Interaction to Next Paint (INP) | Responsiveness | 200 milliseconds or less |
| Cumulative Layout Shift (CLS) | Visual stability | 0.1 or less |
Google evaluates these thresholds at the 75th percentile of page loads, separated between mobile and desktop. See Web Vitals.
These thresholds are useful external references. They should not be the only project requirements.
A publishing platform may also care about the time until article text becomes readable. A web application may need a stricter response target for its primary control. A campaign page may need a specific hero asset to appear before a defined deadline.
The budget should protect the experience the page was built to provide.
Start from the page, not a generic number
A performance budget should reflect actual content and business requirements.
Begin by identifying representative page types:
- Homepage
- Content or article page
- Product or service page
- Search or listing page
- Application screen
- Any conversion-critical route
Do not test only the lightest route.
For each type, document what must appear immediately, what can load later and which third-party services are genuinely required.
A useful review might produce this table:
| Element | Required initially? | Likely cost | Decision |
|---|---|---|---|
| Primary heading and navigation | Yes | Low | Render with the initial document |
| Hero image | Yes | High | Resize, compress and preload only when justified |
| Below-fold gallery | No | High | Lazy-load |
| Analytics | Not for rendering | Variable | Load without blocking the main content |
| Chat widget | No | High | Delay until interaction or explicit consent |
| Decorative animation | No | Variable | Keep only if it fits the runtime budget |
This is where performance becomes a design and product decision.
If everything is marked critical, nothing has been prioritised.
Create an initial budget
A first budget does not need to be perfect. It needs to be explicit and testable.
One approach is to measure the current production page, inspect its main resources and set a limit that prevents further growth. That protects the existing baseline but may preserve an already slow page.
Another approach is to begin from a target experience and work backwards. Decide what must load within the initial view, estimate the available transfer and execution cost, then allocate that allowance between images, scripts, styles and fonts.
In practice, the two approaches can be combined:
- Record the current baseline.
- Identify the most expensive resources.
- Define a reachable short-term ceiling.
- Create a separate target for planned improvements.
- Tighten the enforced budget when those improvements ship.
This avoids setting an aspirational limit that fails every build and is immediately ignored.
Express the budget in Lighthouse
Lighthouse supports a JSON budget containing timing, resource-size and resource-count limits. The official web.dev example uses resourceSizes, resourceCounts and timing entries, with resource sizes expressed in kilobytes.
A project-specific file could begin like this:
[
{
"path": "/*",
"resourceSizes": [
{ "resourceType": "script", "budget": 180 },
{ "resourceType": "image", "budget": 600 },
{ "resourceType": "total", "budget": 900 }
],
"resourceCounts": [
{ "resourceType": "third-party", "budget": 5 },
{ "resourceType": "font", "budget": 3 }
]
}
]
These values are illustrative. They should be replaced with limits derived from the actual site. Resource budgets cover requests observed during the audit, not a guarantee about everything a visitor might load later by scrolling or interacting.
After installing the Lighthouse CLI, the file can be checked with:
lighthouse https://example.com \
--budget-path=./budget.json \
--output=html \
--output-path=./lighthouse-report.html
The resulting report includes a budget section showing which limits passed or failed.
This is useful locally, but a budget that developers have to remember to run is easy to bypass.
Enforce it in continuous integration
Lighthouse CI can run audits as part of the build process and evaluate the results against a budget file.
A minimal lighthouserc.json might look like this:
{
"ci": {
"collect": {
"staticDistDir": "./dist",
"numberOfRuns": 3
},
"assert": {
"budgetsFile": "./budget.json"
}
}
}
This example assumes the production build produces a static site in ./dist. Build it before the audit, change the directory to match the project, and provide a compatible Chrome installation in CI. An application that needs its own server should use Lighthouse CI's server and URL configuration instead.
The official Lighthouse CI configuration supports budgetsFile under the assertion settings. It also notes an important unit difference: budget.json expresses file-size limits in kilobytes, while Lighthouse CI assertions written directly with maxNumericValue use bytes.
Install the CLI as a development dependency and commit the resulting package and lockfile changes:
npm install --save-dev @lhci/cli
After the CI dependency-install and production-build steps, run:
npx lhci autorun
Configure the performance job as a required check before merging or deploying. Then a failed budget assertion can block a regression instead of merely producing an ignored report. The Lighthouse CI getting-started guide covers CI setup.
The failure should lead to a technical or product decision:
- Remove the unnecessary resource.
- Load it later.
- Replace it with a smaller implementation.
- Optimise an existing asset to make room.
- Approve a deliberate budget change with its cost documented.
Silently increasing the limit whenever it fails removes the purpose of the budget.
Separate lab checks from field evidence
Lighthouse CI provides controlled lab measurements. Real users do not share one device, network, location or browser state.
PageSpeed Insights combines two different sources:
- Lighthouse runs a controlled lab test that helps diagnose the page.
- Chrome UX Report, or CrUX, aggregates field data from real users.
Chrome's documentation states this distinction directly and explains that PageSpeed Insights may fall back from URL-level data to origin-level data when a specific URL does not have enough CrUX data. If neither is available, only the Lighthouse lab approximation is shown.
Use the two forms of evidence for different jobs.
Lab testing is useful for catching a regression in a pull request because the environment can be repeated. Field data is useful for checking whether real visitors receive the intended experience across actual devices and connections.
A passing CI check does not prove that users have good performance. Poor field data does not make controlled tests useless. The measurements answer different questions.
Avoid a single budget for every route
Applying one limit to every page can produce misleading results.
A text article and a case-study gallery should not necessarily receive the same image allowance. An authenticated application route may require more JavaScript than a marketing page. A landing page with no application behaviour should not inherit that application budget without a reason.
Use route patterns when the page types have materially different requirements:
[
{
"path": "/blog/*",
"resourceSizes": [
{ "resourceType": "script", "budget": 140 },
{ "resourceType": "total", "budget": 700 }
]
},
{
"path": "/work/*",
"resourceSizes": [
{ "resourceType": "script", "budget": 180 },
{ "resourceType": "image", "budget": 900 },
{ "resourceType": "total", "budget": 1200 }
]
}
]
Different budgets should represent different user needs, not provide a convenient exemption for inefficient pages.
Review every third-party addition
Third-party code deserves its own review because the site team does not fully control its delivery, behaviour or future changes.
Before adding a service, document:
- What user or business task it supports
- Which pages need it
- When it must load
- Its transfer and execution cost
- Whether it creates further requests
- What happens if it fails
- How it will be removed later
Loading a service on every route is not the default answer.
A scheduling widget may only be needed on the contact page. A video player can wait for interaction. A support widget may not need to run before the primary content is usable.
The performance budget makes these costs visible, but the architecture still determines whether they are paid by every visitor.
Treat the budget as a product constraint
A budget works when design, development and content decisions all recognise it.
Design controls the number and treatment of media assets. Development controls delivery, execution and dependency choices. Content decisions can introduce large images, video and embeds after the original build has shipped.
The budget therefore belongs in the same conversation as scope, accessibility and browser support.
Before approving a new feature, ask:
- What does it add to the initial payload?
- Does it delay the main content?
- Does it add main-thread work?
- Can it load after interaction?
- Which existing resource will be removed or reduced?
- How will the effect be measured in production?
This does not mean rejecting every expensive feature. It means accepting the cost deliberately.
A practical review checklist
Before enforcing a web performance budget, verify that:
- Representative routes are tested.
- Mobile and desktop field data are reviewed separately.
- JavaScript, images, fonts and third-party resources have explicit limits.
- The budget file is stored with the project.
- CI runs the check automatically.
- Test settings remain consistent between builds.
- Multiple Lighthouse runs are used to reduce measurement noise.
- Budget changes require review.
- Production field data is monitored alongside lab tests.
- Content editors know which additions can exceed the budget.
The final objective is not a perfect score.
It is a website that remains within an agreed performance envelope as it changes.
Without that constraint, every release can make the page slightly heavier while still appearing acceptable on a developer's machine. With a budget, the cost becomes visible before it becomes permanent.
Conversation
Discussion
Loading comments…