Performance
A site performance budget you can actually enforce
A performance budget caps bytes and milliseconds per deploy. Our home page weighs 327,750 bytes in 23 requests, 196,252 of them fonts: how to set it.
Key points
- Set a budget from the bytes you measured, never from a number copied out of an article.
- The measured home page loads 327,750 bytes in 23 requests, and fonts are 60% of the total.
- Budget per resource type: a total that passes can hide a stylesheet that is out of control.
- Time thresholds only mean something against field data; byte ceilings hold everywhere.
A performance budget is a ceiling on bytes and milliseconds that every deploy has to respect: our home page loads 327,750 bytes across 23 requests, and 196,252 of those bytes are fonts. The budget that follows is 400 KB in total, 200 KB of fonts, 100 KB of JavaScript, 30 KB of CSS and 40 KB of images.
Where the bytes go
| Resource | Measured | Budget | Headroom |
|---|---|---|---|
| Fonts (6 files) | 196,252 B | 200,000 B | +3,748 B |
| JavaScript (2 files) | 90,897 B | 100,000 B | +9,103 B |
| CSS (5 files) | 22,486 B | 30,000 B | +7,514 B |
| HTML | 12,434 B | 15,000 B | +2,566 B |
| Images (4 files) | 3,717 B | 40,000 B | +36,283 B |
| Beacons and SVG | 1,964 B | 3,000 B | +1,036 B |
| Total | 327,750 B | 400,000 B | +72,250 B |
Time has a ceiling too
| Metric | Measured | Budget | State |
|---|---|---|---|
| LCP | 2,856 ms | 2,500 ms | -356 ms |
| TTFB | 167 ms | 800 ms | +633 ms |
| CLS | 0 | 0.1 | within |
| Long task | 486 ms in 3 | 300 ms | -186 ms |
Fonts are 60% of the total: six files for 196,252 bytes, and every weight is a download the browser has to finish before it can show text. Headroom on the total does not excuse a single resource blowing its own cap: when one goes over, the deploy stops anyway, and that is the behaviour you want.
The budget that enforces itself
{
"performance": {
"budget": [
{ "resourceType": "total", "budget": 400 },
{ "resourceType": "stylesheet", "budget": 220 },
{ "resourceType": "script", "budget": 60 },
{ "resourceType": "font", "budget": 50 },
{ "resourceType": "image", "budget": 40 }
],
"timings": [
{ "metric": "largest-contentful-paint", "budget": 2500 },
{ "metric": "cumulative-layout-shift", "budget": 0.1 }
]
}
}
The numbers are kilobytes and they describe transfer, not decompressed weight. Lighthouse takes the file through --budget-path:
npx lighthouse http://localhost:3100/ --budget-path=budget.json \
--output=json --output-path=./lh.json --chrome-flags="--headless"
Lighthouse lists the over-budget items under the budgets category. To fail a pipeline you need Lighthouse CI, which returns a non-zero exit code from the same thresholds in assert.
What to cut first
- Fonts: six files for 196,252 bytes are the first target; two weights are enough, and Inter latin regular alone is 48,256 bytes. Source: web.dev, CC BY 4.0.
- JavaScript: load route chunks only when the route needs them. The two initial files measured 90,897 bytes. Source: web.dev, CC BY 4.0.
- CSS: five files for 22,486 compressed bytes; there is headroom here, and it belongs in rules, not in another stylesheet. Source: MDN, CC BY-SA 2.5.
- Images: 3,717 bytes here because the home page uses four small ones; on a catalogue they become the largest item and belong in AVIF or WebP.
The limitation to name
A budget measures the lab, not your users: 2,672 ms of LCP on simulated Slow 4G is not a field 75th percentile. Bytes hold everywhere because they do not depend on the network; time thresholds need real data, and on a worse connection the 492 ms of long tasks can double.
Where to continue
The EneUI catalogue indexes 111,985 components, for instance sparkles, and the block banner-01: a page with three of those blocks adds images and CSS to the budget. For the thresholds read what Core Web Vitals are, for the cost of scripts third-party JavaScript, for caching cache and CDN, and for architectures frontend frameworks compared.
Frequently asked questions
How heavy should a page be?
There is no universal number: start from the measured weight, set a ceiling just above it, then lower it. Our home starts at 327,750 bytes and the budget is 400,000.
Should the budget be in bytes or milliseconds?
Both. Bytes are deterministic and can be checked in CI; milliseconds depend on network and CPU, so read them next to field data.
Does the budget apply per page or per resource type?
Per resource type. A total that passes can hide an enormous stylesheet offset by a few images, and CSS is the resource that blocks rendering.
How do I make the build fail?
With Lighthouse CI and the thresholds in assert. Lighthouse alone reports over-budget items; Lighthouse CI turns that result into an exit code.
Does the budget ever change?
Yes, when a new feature adds weight on purpose. Raise the ceiling in the commit that explains why, not silently after a failure.