Performance is not a phase. It is a constraint, and constraints that are not enforced are preferences.
We have watched the same cycle repeatedly: a site launches fast, everyone is pleased, and eighteen months later it is heavy again. No single change caused it. Each addition was individually reasonable — a chat widget, a video embed, a tag manager container, an animation library for one page.
Put a number in the CI pipeline
The only intervention that reliably works is a hard budget that fails the build.
On a recent municipal portal we set the budget before design started: 150KB of transferred JavaScript, LCP under one second on a mid-range Android over simulated 3G, and zero accessibility violations. Those were CI gates. A pull request that exceeded any of them did not merge.
This produces uncomfortable conversations early, which is exactly what you want. When someone proposes a library that costs 60KB, the conversation happens at proposal time with a number attached, rather than at launch when it is expensive to reverse.
The budget forces better decisions
Working within a budget changed our architecture in ways we would not have chosen otherwise, and mostly for the better.
The animated hero on this site is a hand-written WebGL shader of about four kilobytes rather than a general-purpose 3D library of a hundred and fifty. It does exactly one thing. It cannot do anything else. That constraint produced a better result than the library would have, because it forced us to design the effect rather than assemble it.
Similarly, we ship no icon library. The icons are an in-house SVG set where each icon costs only what it uses. There is no barrel import quietly pulling in nine hundred glyphs.
Third-party scripts are the largest single risk
Most performance regressions in production come from things engineering did not ship. A tag manager container is a loaded gun pointed at your Core Web Vitals, because anyone with access can add a script without a code review.
We load analytics only after consent, defer everything non-essential until the main thread is idle, and maintain an explicit allow-list in the Content Security Policy. Adding a new third-party script requires a CSP change, which requires a pull request, which means somebody reviews it. That friction is deliberate.
Measure the device your users have
Lighthouse on a developer laptop on fibre tells you almost nothing about a user on a R2 000 handset on a congested cell.
We test on throttled CPU and simulated 3G by default, and on a physical device mix for anything with a field workforce. The gap between those two measurements is routinely a factor of five.
Accessibility belongs in the same gate
The identical argument applies. Automated axe checks catch roughly a third of real accessibility issues, which is not sufficient on its own but is enough to stop regressions — and they run on every pull request. The remaining two-thirds need a manual keyboard and screen-reader pass, which we do before each release rather than continuously.
The point is the same in both cases: a standard nobody can violate accidentally is the only kind that survives eighteen months of ordinary development.