Build Systems/Rendering
Enforce a performance budget in CI
Fail the pull request, not the production deploy — a Lighthouse CI gate on three representative URLs.
// read first
A performance budget nobody enforces is a note in a document. Put it in CI and it becomes a property of the codebase.
Three URLs is the right number: a marketing page, a long article, and the heaviest template you ship. Adding more slows the gate without finding new classes of regression.
Add the gate
Step 01
Write the budget down
Assertions live in a config file next to the workflow, so changing the budget shows up in review as a diff rather than a discussion.
lighthouserc.jsonjson { "ci": { "collect": { "url": [ "http://localhost:3000/", "http://localhost:3000/blog/seo-nextjs-app-router", "http://localhost:3000/docs/server-setups/provision-ubuntu-node-server" ], "numberOfRuns": 3, "settings": { "preset": "desktop" } }, "assert": { "assertions": { "categories:performance": ["error", { "minScore": 0.9 }], "categories:seo": ["error", { "minScore": 1 }], "categories:accessibility": ["error", { "minScore": 0.95 }], "largest-contentful-paint": ["error", { "maxNumericValue": 2500 }], "cumulative-layout-shift": ["error", { "maxNumericValue": 0.1 }] } } } }Step 02
Run it against a production build
Never audit the dev server. It ships unminified bundles and no caching, so the numbers are meaningless and the gate will either always fail or be set so loose it never fires.
.github/workflows/lighthouse.ymlyaml - run: npm ci - run: npm run build - run: npx --yes @lhci/cli autorun --config=lighthouserc.jsonStep 03
Median of three runs, not one
A single Lighthouse run on shared CI hardware varies by ten points or more. Three runs with the median taken is the smallest number that produces a gate people trust rather than retry.
What to do when it fails
| Assertion | Usual cause | First thing to check |
|---|---|---|
| LCP | Hero image lazy or unsized | priority + explicit width/height |
| CLS | Web font or late banner | next/font, reserve space |
| TBT | Client component too high in the tree | Push "use client" to leaves |
| SEO < 100 | Missing meta description or canonical | generateMetadata on the route |
// related
From the rest of the site.
A Core Web Vitals Budget You Can Actually Ship
Concrete numbers, the handful of techniques that move them, and how to stop a fast site from slowly getting slow.
