Web performance is a budget, not a sprint
Every performance sprint I have been on made the site fast for about four months. The teams whose sites stayed fast did something different, and it was not technical.
Contents
I have been hired twice to make a slow site fast. Both times it worked. Both times I checked back a year later and the site was slow again.
That is not a failure of the optimisations. It is what happens when speed is a project instead of a constraint. Nobody sets out to make a site slow — it happens one reasonable decision at a time, and if nothing pushes back, the reasonable decisions add up.
The three numbers, briefly
LCP — how long until the biggest thing on screen is painted. Aim under 2.5s. Almost always an image, a font, or a server that thought about it too long.
INP — how long between a user interacting and the page visibly responding, across the whole visit. Aim under 200ms. This replaced FID in 2024, and it is much harder to game, because it measures every interaction rather than only the first.
CLS — how much things jump around. Aim under 0.1. Nearly always images without dimensions, ads, or a font swap that changes the metrics.
Learn what each one blames, then stop reading about them and go and look at your own.
Lab numbers lie, and everyone knows it
Lighthouse on your MacBook on office wifi tells you about your MacBook on office wifi. It is a debugging tool. It is not a measurement of your site.
Field data is what counts, and the good news is that collecting it is about ten lines:
import { onLCP, onINP, onCLS } from 'web-vitals';
const send = (metric) => {
navigator.sendBeacon(
'/vitals',
JSON.stringify({
name: metric.name,
value: metric.value,
rating: metric.rating,
path: location.pathname,
})
);
};
onLCP(send);
onINP(send);
onCLS(send);
Then look at the 75th percentile, not the average. Averages hide the users having a bad time, and those are the users who leave.
The first time a client saw their real p75 next to their Lighthouse score, the conversation about performance changed permanently. Their score was 94. Their real LCP was 4.1 seconds. Both numbers were correct.
The things that are actually slow
After enough of these audits, the list stops surprising you. In rough order of how often it is the answer:
Images. Not compressed, not sized, not lazy, wrong format. Modern formats plus width, height, loading and fetchpriority on the hero fix most of it in an afternoon.
Third-party scripts. Analytics, chat widgets, tag managers, A/B testing. Each one was added by somebody who did not have to pay for it. A tag manager is a hole in your performance budget through which anybody in marketing can pour arbitrary JavaScript, and nobody will tell you.
Fonts. font-display: swap, preload the one face used above the fold, subset it, self-host it. And check the fallback’s metrics — size-adjust and ascent-override exist precisely so the swap does not shove your layout around.
The JavaScript bundle. Usually a date library, a chart library on a page with no chart, or an icon set imported whole. Run the bundle analyser before you run anything clever.
The server. Sometimes TTFB is 800ms and the entire front-end conversation is a distraction. Check this first; it takes thirty seconds.
The part that makes it stick
Here is what the teams whose sites stayed fast actually did, and none of it is a technique.
They wrote the budget down. Not “the site should be fast”. Specific: JavaScript under 170 KB compressed, LCP under 2.5s at p75, no third-party script over 40 KB. Numbers you can fail.
They enforced it in CI. A bundle size check on every pull request, failing the build. This is the single highest-leverage thing on the list. Once “you cannot merge this” replaces “we should look at that sometime”, the problem stops growing.
- name: Bundle size
run: npx size-limit
They made somebody sign off on exceptions. Not to block anything — to make the cost visible. “This adds 60 KB, are we happy?” is a two-minute conversation that changes decisions. Nobody has that conversation by accident.
They put the graph on a screen people walk past. p75 LCP, updated daily. It sounds like theatre. It works, because a number that trends the wrong way in public gets fixed.
Why this is a culture problem
A performance sprint takes a site from bad to good and changes nothing about why it got bad. Four months later somebody adds a chat widget, somebody else imports a library for one function, a designer asks for a fourth font, and each of those is individually fine.
A budget makes the cost visible at the moment of the decision, which is the only moment anybody can act on it. That is the entire difference between a fast site and a site that was fast once.