A budget you do not enforce in CI is a preference. Here is how we make it a constraint.
Every team has a performance budget. Very few have one that can fail a build, which is another way of saying very few have one.
Numbers we actually enforce
- LCP under 1.8s on a throttled mobile profile, measured on the three highest-traffic routes.
- Total JS under 180KB gzipped on first load; a new dependency that breaks it needs a written reason.
- CLS under 0.02 — in practice this means every image ships with intrinsic dimensions.
- No third-party script on the critical path. None.
- name: Lighthouse CI
uses: treosh/lighthouse-ci-action@v11
with:
urls: |
${{ env.PREVIEW_URL }}/
${{ env.PREVIEW_URL }}/work
budgetPath: ./perf-budget.json
uploadArtifacts: true
# exit code is not swallowed: a breach fails the PRThe cheap wins, in order
Serve AVIF with a WebP fallback and correct width and height attributes. Preload exactly one LCP image and nothing else. Subset your fonts and self-host them. Defer anything below the fold, including the animation library.
Motion should be the last thing to load and the first thing to disable when a visitor asks for less of it.
Every scroll effect on this site is gated behind prefers-reduced-motion, and the content renders identically without any of it. That is not an accessibility feature bolted on at the end; it is the only way to be sure the page works before the JavaScript arrives.
What happens when a PR breaches the budget
The PR fails. Not with a warning in the logs that everyone ignores, but with a red cross next to the commit. The author has two choices: revert the dependency, or write a one-paragraph justification that gets reviewed by the team. Most of the time, they revert it.
- If the breach is in LCP, check what changed in the critical path — new fonts, new images, new scripts.
- If the breach is in bundle size, run the bundle analyzer and look for the new import that pulled in a heavy transitive dependency.
- If the breach is in CLS, check for images or embeds without explicit dimensions.
The discipline is not in having the budget. It is in letting it fail the build, every time, without exception. That is what turns a preference into a constraint.