Free tool
Performance budget calculator
How many bytes can your LCP element afford? Set a target, pick a connection, get the byte budget — modelled on TCP slow start, not naive bandwidth math.
Where your 2500 ms goes
- Server response (TTFB) 600 ms 24% within Google's 800 ms bar
- Discovery round trips 300 ms 12% 2 hops after the HTML × 150 ms RTT
- Render & decode 250 ms 10% assumed, not measured
- Left for bytes 1350 ms 54% everything the budget below is made of
Your critical-path budget
Naive “bandwidth × time” maths would promise 263.7 KiB for the same window — 6% more than a real connection can deliver. See the two curves.
How you spend it
The total is the contract; this cut is yours. Drag a share and the unlocked rows absorb the difference, so the split always adds to 100%.
For product, article and marketing pages whose hero image is the LCP element.
- 8% 19.8 KiB
- 12% 29.7 KiB
- 25% 62.0 KiB
- 45% 111.5 KiB
- 10% 24.8 KiB
Allocated: 100% · 247.9 KiB
Bytes are the currency here because they are the one thing you control on every connection — what a kilobyte actually costs puts a time price on each one.
Export it for Lighthouse CI
The file is stricter than the model. This calculator answers “how many bytes fit on the LCP critical path”. Lighthouse's resourceSizes caps the transfer of every observed resource of that type on the page — so 400 KiB of below-the-fold images count against the file and not against the model. A page can satisfy the budget above and still fail this file. Treat the export as the CI ceiling and the number above as the design target.
Measure a URL above and this offers that page and its route family too.
This file will govern /* — every route on the origin, including ones you never modelled here. It carries 5 resource caps, a total, and one timing budget (LCP).
Read the file before you download it (459 characters)
[
{
"path": "/*",
"resourceSizes": [
{ "resourceType": "document", "budget": 19 },
{ "resourceType": "stylesheet", "budget": 29 },
{ "resourceType": "script", "budget": 61 },
{ "resourceType": "image", "budget": 111 },
{ "resourceType": "font", "budget": 24 },
{ "resourceType": "total", "budget": 244 }
],
"timings": [
{ "metric": "largest-contentful-paint", "budget": 2500 }
]
}
]Lighthouse CI reads it through assert.budgetsFile — an assert option, so nothing happens until lhci assert (or lhci autorun) runs after collect. The path is relative to the directory you run lhci from:
{ "ci": { "assert": { "budgetsFile": "./budget.json" } } }
budgetsFile cannot be combined with assertions, assertMatrix or preset. A config that already has assertions keeps them and gates the budget in a second call that opts out of the config file — lhci assert --no-lighthouserc --budgetsFile=./budget.json — because lhci merges the file's assert block into every run and would refuse the pair. Or restate the budget as resource-summary:<type>:size assertions, which take bytes where this file uses kilobytes, plus largest-contentful-paint and cumulative-layout-shift maxNumericValue entries for its two timings. Then a build that busts the budget fails.
Before you spend the budget on a specific change, the fix simulator prices that change against your own page; the sample-size planner says how many runs it takes to prove it moved.
Why the budget is smaller than the pipe suggests
- Naive bandwidth × time — 263.7 KiB
- TCP slow start — 247.9 KiB
A new connection starts at 10 segments = 14,600 bytes (RFC 6928) and doubles every round trip until it saturates the link. The gap between the two lines is the budget a naive model would hand you and the network would never deliver.
The same page on every connection
Your inputs, unchanged, on each preset. A budget is a property of the network as much as of the page.
| Connection | Transfer window | Budget | Use this profile |
|---|---|---|---|
| Slow 4G — Lighthouse mobile default · selected | 1350 ms | 247.9 KiB | |
| 4G | 1310 ms | 906.0 KiB | |
| 3G | 1050 ms | 129.1 KiB | |
| Cable / desktop | 1594 ms | 970.1 KiB |
What each lever is worth
One change at a time, everything else held at your current inputs. Work on the page first — the biggest single number here is “promise users a slower page”, which buys budget by giving up the thing the budget is for.
| Change | Budget becomes | Difference |
|---|---|---|
| Change the page | ||
| Cut TTFB by 200 ms a cache or a faster origin | 286.9 KiB | +39.1 KiB |
| Remove one discovery hop preload or inline it — worth 150 ms of RTT | 277.1 KiB | +29.3 KiB |
| Add one discovery hop one more chained request costs 150 ms | 218.6 KiB | −29.3 KiB |
| Change the promise | ||
| Loosen the target by 500 ms the same page, a slower promise | 345.5 KiB | +97.7 KiB |
| Tighten the target by 500 ms the same page, a faster promise | 150.2 KiB | −97.7 KiB |
The model (and why naive bandwidth math lies to you)
Dividing “1.6 Mbps × time” into bytes overestimates small budgets badly, because TCP doesn't start at line rate. A new connection starts with an initial congestion window of 10 segments = 14,600 bytes (RFC 6928) and doubles it every round trip until it saturates the link. On a 150 ms-RTT mobile connection that means the first ~450 ms move only ~100 KiB — no matter how fat the pipe is.
This calculator therefore computes:
Time budget
LCP target − TTFB − render allowance − ((sequential requests − 1) × RTT). Every hop after the HTML in a discovery chain (HTML → CSS → font) burns a full round trip before its first byte — the HTML's own round trip is already inside your TTFB.Bytes from time
TCP slow start from 10 segments, doubling per RTT, capped at the bandwidth-delay product, line-rate after that.
A suggested split
Weighted toward the LCP image because on most commercial pages the LCP element is an image. The total is the contract; the split is a starting point you can re-cut above.
Deliberate simplifications
One connection (HTTP/2/3 multiplexing shares it anyway), no packet loss, no BBR, render allowance as a flat constant instead of a CPU simulation. These all make the budget slightly optimistic — treat it as an upper bound, which is exactly what a budget should be.
And if you prefill from your page: the field p75 gap you see is closed by the whole path — TTFB, redirects, the discovery chain, render on real devices — not by bytes alone. The byte budget is the piece this calculator disciplines. Treat it as necessary, not sufficient — and see when a lab model actually predicts the field for how far a number like this one carries.
Proving a change actually landed
Getting under budget is a lab result. Whether the change moved anything is a statistical question: how many Lighthouse runs prove a change covers why a single before/after pair proves nothing, and the sample-size planner turns your own variance into a run count.
Sources
- RFC 6928 — Increasing TCP's Initial Window
- Lighthouse performance scoring & throttling (Slow 4G: 1.6 Mbps / 150 ms RTT)
- web.dev — Largest Contentful Paint thresholds
- WebPageTest connection presets (3G, 4G, Cable)
- budget.json format (the spec Lighthouse CI reads; Lighthouse itself dropped budgets in 12.0)
- Lighthouse CI configuration reference —
assert.budgetsFile
Want the budget enforced against your real page?
A PageSpeedAudit report measures where your actual bytes go, which ones block your LCP, and the exact code changes that bring you under budget.
Get your auditThe other half of the loop
Watch this URL's real-user data — the day-28 verdict, emailed
Everything above is a lab model: a promise about bytes, made today. A budget is finally a promise about the field. We fetch your CrUX p75s now as the baseline, email the verdict when the 28-day window has turned over, and alert you if any vital crosses out of “good”. If your site is below CrUX's traffic threshold, we say so honestly — and tell you the moment Chrome starts publishing data.