Performance budgets catch regressions at commit time. No LCP slip makes it to production.
What a Performance Budget Actually Is
A performance budget is a set of numeric limits for key metrics. If your code changes violate those limits, the build fails. It's not a nice-to-have target — it's a gate. Same logic as linting or unit tests, except the metric is "does your site load in time?"
Typical budgets for marketing/conversion sites:
- LCP ≤ 1500ms (largest paint in viewport on mobile 4G)
- INP ≤ 200ms (button click to visible response)
- CLS ≤ 0.1 (layout shift; we aim for 0.0)
- Performance score ≥ 95 (Lighthouse overall grade)
These numbers aren't arbitrary. Google's algorithm rewards them. Users bounce on slower sites. Conversions measurably drop when LCP exceeds 2.5s. Performance budgets codify the business rule: "we ship nothing slower than this."
Why "We'll Optimise Later" Fails
Without a gate, "we'll fix performance next quarter" becomes "we'll never fix it." New features land, tech debt compounds, the codebase bloats. By the time someone runs Lighthouse, the site is 3s LCP and a rewrite is cheaper than a refactor.
With Lighthouse CI, there is no "later." The regression is caught at the developer's machine before they push. They either optimise the change or revert it. Performance stays constant. No death by a thousand commits.
Lighthouse CI Setup (Real Config)
Step 1: Install and auth.
{`npm install --save-dev @lhci/cli@latest @lhci/utils
lhci wizard --github # links your repo, creates GitHub status checks
`}
Step 2: Create lhci.json in your repo root.
{`{
"ci": {
"collect": {
"url": ["https://yoursite.com/", "https://yoursite.com/pricing"],
"numberOfRuns": 3,
"settings": {
"configPath": "./lighthouserc.json"
}
},
"assert": {
"preset": "lighthouse:recommended",
"assertions": {
"categories:performance": ["error", { "minScore": 0.95 }],
"metrics:largest-contentful-paint": ["error", { "maxNumericValue": 1500 }],
"metrics:interaction-to-next-paint": ["error", { "maxNumericValue": 200 }],
"metrics:cumulative-layout-shift": ["error", { "maxNumericValue": 0.1 }]
}
},
"upload": {
"target": "github"
},
"server": {}
}
}
`}
That's it. Three runs per URL (averages out noise). Assertions fail the build if metrics slip. The GitHub action reports pass/fail as a status check on your PR.
Step 3: Wire it into GitHub Actions.
{`name: Lighthouse CI
on: [push, pull_request]
jobs:
lhci:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 20
- run: npm ci
- run: npm run build
- run: npm install -g @lhci/cli@latest
- run: lhci autorun
env:
GITHUB_TOKEN: \${{ secrets.GITHUB_TOKEN }}
LHCI_GITHUB_APP_TOKEN: \${{ secrets.LHCI_GITHUB_APP_TOKEN }}
`}
On every push, the action builds your site, runs Lighthouse three times, compares against your budget, and posts a status check. Red X if you failed. Green check if you passed.
Real Velocity X lhci.json
Here's what we actually use. The numbers are tuned for conversion-focused marketing sites:
{`{
"ci": {
"collect": {
"url": [
"https://aidxn.com/",
"https://aidxn.com/brand-strategy",
"https://aidxn.com/web-design",
"https://aidxn.com/blog"
],
"numberOfRuns": 3,
"settings": {
"chromeFlags": "--disable-gpu --no-sandbox"
}
},
"assert": {
"assertions": {
"categories:performance": ["error", { "minScore": 0.95 }],
"metrics:largest-contentful-paint": ["error", { "maxNumericValue": 1500 }],
"metrics:interaction-to-next-paint": ["error", { "maxNumericValue": 200 }],
"metrics:cumulative-layout-shift": ["error", { "maxNumericValue": 0.1 }]
}
},
"upload": {
"target": "github"
}
}
}
`}
Four pages: home, two service categories, blog index. Three runs each. If any page misses the budget, the build fails. Simple. Measurable. Enforced.
Six FAQs
Does Lighthouse CI test mobile or desktop?
Both. By default it runs mobile (the harder target). You can override via settings. We only gate mobile because if you pass mobile, desktop almost always passes.
What if a regression is legitimate?
You have two options: (1) fix it, or (2) raise the budget. Raising the budget is a deliberate commit that the team sees. It forces conversation: "Did we agree to slower load times?" Usually, the answer is no, and the regression gets fixed.
How often do you run Lighthouse CI?
On every push and every PR. It takes 2–3 minutes. You see the result before you merge. Catch regressions before they're in main.
Can I test dynamic pages?
Yes, but they need to be pre-built or pre-rendered. Lighthouse CI audits static URLs, not user flows. For Astro static sites, every page renders at build time — test them all. For dynamic dashboards, test the shell and key pages only.
What about third-party script overhead?
That's the point. If you add a tracking script that drops LCP by 300ms, the build fails immediately. You either defer it (Partytown), lazy-load it, or ship without it. The budget forces the conversation.
How do I raise budgets safely?
Commit a lhci.json change with the new threshold and a message like "Raising LCP budget to 1800ms due to new hero video." Team sees it, approves it, it's in the git history forever. Budgets aren't secret.
The Bottom Line
Lighthouse CI turns performance from a "nice to have" into a "ship or don't." Regressions are caught at commit time, not production. Your team stays within budget, your users stay happy, your conversions stay high. Combined with the Core Web Vitals pattern stack, Lighthouse CI ensures the speed stays.
Ready to lock in performance? Check Velocity X pricing to see how automated budgets fit into a modern build pipeline.