A website performance budget is a set of measurable limits for a page before it ships. Instead of asking whether a website “feels fast,” developers agree on limits for transferred bytes, resource requests, loading, visual stability, and main-thread work, then check the deployed page under repeatable conditions. The result is a release rule that catches performance regressions while they are still reviewable.
This guide shows how to choose useful limits, measure the right values, add a Lighthouse performance budget to CI, and use Slop Audit as deployed-page evidence without confusing a lab run with real-user performance.
Website performance budget checklist
Use this sequence for one important production route:
- Name the user task and the device, network, viewport, and cache conditions.
- Measure the current deployed route several times before choosing a limit.
- Set budgets for user outcomes and the resources your team controls.
- Give each budget a warning level and a release-blocking level.
- Check the built site in CI and the public preview after deployment.
- Save enough evidence to identify the resource or metric that regressed.
- Compare the same route and conditions after every material change.
- Review the limits when the product or audience changes.
A budget should change a release decision. A number in a document that nobody checks is only a performance wish.
What a website performance budget measures
MDN defines a performance budget as a limit used to prevent regressions. The limit can apply to a file, a resource type, a whole page, a timing metric, or a custom product metric. web.dev's performance budget guide similarly treats the budget as a reference point for deciding what a page can afford.
Use three layers rather than forcing every concern into one score.
Field outcome budgets
Field data describes visits from real users. For public sites with enough traffic, track the current Core Web Vitals at the 75th percentile, split by mobile and desktop. Google's Web Vitals guidance defines the “good” thresholds as:
- Largest Contentful Paint at or below
2.5 s; - Interaction to Next Paint at or below
200 ms; - Cumulative Layout Shift at or below
0.1.
These are useful outcome boundaries, not a complete product definition. A checkout, documentation page, and media gallery may need different supporting budgets even when they share the same Core Web Vitals goals. The Core Web Vitals guide for developers explains how field and lab measurements answer different questions.
Lab behavior budgets
Lab tests give developers repeatable feedback before enough field data exists. Useful limits include:
- lab LCP for the main content;
- lab CLS during the bounded test window;
- a blocking-time or long-task limit for startup work;
- a route-specific milestone, such as time until the search box is usable.
Keep the environment fixed and name the metric accurately. A custom sum of long-task blocking periods is not field INP, and a single synthetic LCP result is not proof that most users receive the same experience. The JavaScript long tasks guide shows how to label a blocking-time proxy without presenting it as Lighthouse TBT or field INP.
Resource budgets
Resource budgets protect the inputs that usually cause a regression:
- total transferred page weight;
- JavaScript, CSS, image, and font transfer sizes;
- total request count;
- third-party request count;
- the largest individual script, image, or font.
These limits are often more stable in CI than timing metrics. If a pull request adds 180 KB of JavaScript or five third-party requests, the failure points directly to a change the team can review. Timing still matters, but resource limits make the cause easier to own.
Start from a measured baseline
Do not copy a universal page-weight number and call the work finished. Start with the audience, the route, and the current production evidence.
For example, a small team might define this release contract for a public documentation route:
Test route: /docs/getting-started
Viewport: 375 x 812
Cache: cold
Runs: 5; use the median
Warning limits:
- total transfer: 1,200 KB
- JavaScript: 240 KB
- requests: 60
- lab LCP: 2,500 ms
- lab CLS: 0.10
Release-blocking limits:
- total transfer: 1,500 KB
- JavaScript: 300 KB
- requests: 75
- lab LCP: 3,500 ms
- lab CLS: 0.20
Those values are an example, not a recommendation for every website. A better process is:
- Record several runs for the current route under controlled conditions.
- Remove obvious waste before accepting the baseline.
- Set the warning close enough to expose gradual growth.
- Set the error boundary where the team has agreed to stop or obtain an explicit exception.
- Write down the owner and the response when a limit is exceeded.
MDN recommends separate warning and error levels so teams can plan work before a regression becomes a hard failure. Keep the distance between them small enough that warnings lead to action rather than becoming permanent background noise.
Measure page weight and requests carefully
Open the deployed route in Chrome DevTools, select Network, disable the cache for a cold-load test, and reload. Chrome's Network panel reference explains that the status bar shows the request count plus transferred and loaded resource sizes. “Transferred” is the compressed network cost; “resources” is the decoded size. Record which one your budget uses.
If you measure in page JavaScript, the Resource Timing API exposes entries for fetched resources:
const resources = performance.getEntriesByType("resource");
const summary = resources.reduce(
(result, entry) => {
result.requests += 1;
result.transferBytes += entry.transferSize;
result.byType[entry.initiatorType] =
(result.byType[entry.initiatorType] ?? 0) + entry.transferSize;
return result;
},
{ requests: 0, transferBytes: 0, byType: {} },
);
console.table(summary);
Know the blind spots before using that total as a gate. MDN's `transferSize` reference notes that a value can be zero for a cached resource or for a cross-origin resource that does not expose timing information with Timing-Allow-Origin. The initial HTML document also is not a resource entry. Browser evidence is most useful when the route, cache state, origins, and measurement method stay consistent.
When images dominate the result, work through the website image optimization checklist. When JavaScript transfer looks acceptable but the main thread remains blocked, inspect execution using the long-task workflow rather than cutting files only by byte size.
Add a Lighthouse performance budget to CI
Lighthouse supports route-specific resource-size and request-count budgets. Save a budgets.json file in the repository:
[
{
"path": "/*",
"resourceSizes": [
{ "resourceType": "total", "budget": 1500 },
{ "resourceType": "script", "budget": 300 },
{ "resourceType": "image", "budget": 700 }
],
"resourceCounts": [
{ "resourceType": "total", "budget": 75 },
{ "resourceType": "third-party", "budget": 8 }
]
}
]
The size values in this file are kilobytes, while count values are requests. Then connect the file to Lighthouse CI:
module.exports = {
ci: {
collect: {
url: ["http://localhost:4173/docs/getting-started"],
numberOfRuns: 5,
settings: {
budgetPath: "./budgets.json",
},
},
assert: {
assertions: {
"performance-budget": "error",
"largest-contentful-paint": [
"warn",
{ maxNumericValue: 2500, aggregationMethod: "median" }
],
"cumulative-layout-shift": [
"warn",
{ maxNumericValue: 0.1, aggregationMethod: "median" }
]
},
},
},
};
The Lighthouse CI configuration reference documents budgetPath, the performance-budget assertion, numeric assertions, and multiple-run aggregation. Pin the tool version, keep the runner stable, and investigate normal variance before making a timing metric block every pull request.
Build checks and deployed-page checks serve different purposes. A local production build catches owned bundle growth quickly. A public preview includes CDN behavior, headers, third-party scripts, fonts, and deployment configuration. Use both when the route depends on both. The website audit with GitHub Actions guide shows how to attach deployed audit evidence to a pull request without exposing the scan token.
Decide what happens when the budget fails
A performance budget works only when the team agrees on the response. For each release-blocking limit, choose one of these outcomes:
- remove or defer the new resource;
- optimize an existing resource to make room;
- redesign the feature to spend less;
- approve a documented exception with an owner and review date;
- change the budget because the route's product contract has genuinely changed.
Do not silently raise the number until CI turns green. A valid exception explains the user value, the measured cost, the routes and devices affected, the evidence used, and when the decision will be reviewed.
Warnings need ownership too. A warning that stays open for months trains developers to ignore the check. Create a task, name an owner, or tighten the policy so warnings represent work the team will actually consider.
Keep the website performance budget after launch
Budgets prevent future growth only when measurements continue. Save the route, test conditions, build revision, and results for each release. Compare like with like, and treat a missing measurement as unavailable evidence rather than a pass.
Use field monitoring to confirm whether synthetic improvements reach real users. Use lab monitoring to catch a specific regression quickly. If the field result gets worse while the resource budget stays flat, investigate server response, rendering, third-party behavior, device mix, and interactions rather than assuming the budget proves the page is healthy.
The website regression testing checklist provides a broader release routine for metadata, mobile layout, accessibility, crawl controls, and performance. Performance should be one reviewable part of launch readiness, not an isolated score that hides broken behavior elsewhere.
Check a website performance budget with Slop Audit
Run a Slop Audit against the public preview or production URL you plan to ship. During its bounded mobile Browser Run, Slop Audit records resource request count, approximate transferred bytes, lab LCP, lab CLS, and a long-task blocking-time proxy. Its current request-and-transfer rule passes at no more than 80 resource requests and 2,500,000 transferred bytes, and warns when either boundary is exceeded.
Treat those built-in boundaries as launch triage, not your custom product budget. Slop Audit does not import your budgets.json, replace real-user monitoring, or certify performance. Transfer totals can also be incomplete when browser timing data is unavailable. Use the report to capture evidence from the deployed page, identify which performance area needs investigation, and compare a rescan after the fix.
Signed-in projects can add the deployed audit to CI and schedule monitoring for new or worsened findings. That gives the budget a practical conversion path: establish a baseline, enforce the limits your team owns, and keep checking the same public route for regressions.
Website performance budget release checklist
- Tie every limit to a named route, user task, and test environment.
- Track both user outcomes and controllable resource inputs.
- Separate field Core Web Vitals from synthetic lab evidence.
- Record transferred bytes and decoded bytes under the correct names.
- Account for cache state and cross-origin Resource Timing gaps.
- Use warning and error boundaries with explicit owners.
- Run multiple samples before blocking a release on a noisy timing metric.
- Check the production build and the public preview where appropriate.
- Save evidence that identifies what changed, not only a score.
- Rerun the same route after a fix and keep monitoring after launch.
Start with one route that matters, one measured baseline, and a few limits your team can explain. A small website performance budget that changes release decisions is more useful than a large dashboard nobody trusts.