Web development
Lighthouse CI with budgets: assertions that block a merge
A lighthouserc.json with numberOfRuns, assertions on LCP, CLS and TBT, and byte budgets: the setup that blocks a merge when the site gets slower.
Key points
- `lhci autorun` collects, compares with the thresholds and uploads: an `error` assertion fails the job, `warn` does not.
- `numberOfRuns: 3` keeps the median and cuts runner variance; the 2,500 ms, 0.1 and 200 ms limits are the web.dev ones.
- Byte budgets (`resource-summary`) hold steadier than scores because they do not depend on machine speed.
Short answer: install @lhci/cli, describe URLs and thresholds in lighthouserc.json, and run lhci autorun in CI. When an error assertion misses, the job fails and the merge stays out.
Install and run it once by hand
npm i -D @lhci/[email protected]
npx lhci autorun --collect.url=http://localhost:3000/
autorun takes three steps: collect starts headless Chrome and saves the reports, assert compares the numbers with the thresholds, upload publishes the result. The first pass exists to show the real numbers before you choose thresholds.
The configuration file
{
"ci": {
"collect": { "url": ["http://localhost:3000/"], "numberOfRuns": 3 },
"assert": {
"assertions": {
"categories:performance": ["error", { "minScore": 0.9 }],
"largest-contentful-paint": ["error", { "maxNumericValue": 2500 }],
"cumulative-layout-shift": ["error", { "maxNumericValue": 0.1 }],
"total-blocking-time": ["error", { "maxNumericValue": 200 }]
}
},
"upload": { "target": "temporary-public-storage" }
}
}
numberOfRuns: 3 runs Lighthouse three times and keeps the median: the cure for variance on a shared runner. The 2,500 ms, 0.1 and 200 ms limits are the ones web.dev publishes for LCP, CLS and TBT.
error, warn and the rest
An error assertion ends the job, warn prints and moves on. Start with warn on a project already in production, then switch to error once the numbers sit under the limits: a budget that blocks the team from day one gets switched off within a week.
Budgets in bytes, not only scores
{
"ci": {
"assert": {
"assertions": {
"resource-summary:script:size": ["error", { "maxNumericValue": 180000 }],
"resource-summary:total:size": ["error", { "maxNumericValue": 600000 }]
}
}
}
}
resource-summary assertions count transferred bytes per resource type. They hold steadier than scores because they do not depend on runner speed: a byte budget changes when the code changes.
A named limitation
Lighthouse CI measures in the lab with simulated throttling: it does not replace CrUX field data, which comes from real users. A score of 100 does not promise an LCP under 2.5 s on a slow phone.
Where to continue
The catalogue holds the metric card, the donut chart and the chart category blocks. For the rest read the Lighthouse CI thresholds explained, the PerformanceObserver guide and the comparison Vercel analytics or self-hosted.
Frequently asked questions
Is Lighthouse CI the same tool as Lighthouse?
Yes. @lhci/cli orchestrates Lighthouse: collect starts the runs, assert compares the results, upload stores the reports. The measurement engine is the same one.
How many runs give a stable result?
Three is the practical minimum, five on a very noisy runner. Lighthouse CI uses the median run, so a single outlier does not decide the outcome.
Do I need to host a server for the reports?
No. With upload target temporary-public-storage the reports stay available for a few days on public storage. History needs an LHCI server, which is a separate project.
Can I block regressions only, not absolute values?
Yes, and that is the most useful setup on a project already slow: compare the current build with the previous one and allow a tolerance. Absolute assertions come after the numbers sit under the limits.
Does Lighthouse CI measure on a real phone?
No. It emulates a phone with throttled CPU and network inside headless Chrome: useful to compare builds, not to estimate the experience on a specific device.