Web Performance
Website performance and Core Web Vitals: an engineering budget
Treating LCP, INP and CLS as build-time budgets rather than a post-launch cleanup exercise.
Website performance is almost never lost in one place. It leaks: a font here, an analytics tag there, a hero image nobody resized, a component library imported whole for one button. By launch the site is slow and no single decision is to blame.
The fix is not a post-launch optimisation sprint. It is a budget agreed during design and enforced during the build.
What the three metrics actually measure
- Largest Contentful Paint (LCP) — when the main content of the page becomes visible. In practice this is almost always the hero image or the headline.
- Interaction to Next Paint (INP) — how quickly the page responds to input. Poor INP nearly always means the main thread is busy with JavaScript.
- Cumulative Layout Shift (CLS) — how much content moves during load. Usually caused by images without dimensions, late-loading fonts, or injected banners.
They are field metrics, gathered from real visitors on real devices and connections. That distinction matters: a lab score on a fast laptop can look excellent while the field data from mid-range phones on mobile networks tells a different story.
Setting the budget before design
A performance budget is a small set of numbers agreed before layouts are drawn, because the expensive decisions are made in design, not in code. A full-bleed video hero is a performance decision. So is a carousel of high-resolution photography, and so is a headline in a font that has to be downloaded before anything is legible.
A workable starting budget for a marketing site:
- JavaScript: under 150 KB compressed on the critical path
- CSS: a single stylesheet, under 40 KB compressed
- Hero image: under 150 KB, served in a modern format at the displayed size
- Fonts: two families maximum, self-hosted, subset, preloaded
- Third-party scripts: an explicit, reviewed list — not an open door
Fixing LCP: it is usually the hero
LCP has three components — time to first byte, resource load time, and render delay. Attack them in that order.
- Serve HTML from a CDN edge so first byte arrives quickly regardless of visitor location.
- Give the LCP image an explicit
fetchpriority="high"and never lazy-load it. Lazy-loading the hero is one of the most common self-inflicted performance wounds. - Serve responsive sizes with
srcsetin AVIF or WebP so a phone does not download a desktop-sized image. - Preload the primary font and use
font-display: swapso text is legible before the webfont arrives.
If the LCP element is text rather than an image, the font is usually the bottleneck, and self-hosting a subset removes both the DNS and connection cost of a third-party font host.
Fixing INP: ship less JavaScript
INP problems are main-thread problems. The most effective fix is not optimising the JavaScript you ship — it is shipping less of it.
- Ask whether a component needs a framework at all. A navigation toggle, an accordion and a scroll reveal are a few dozen lines of vanilla JavaScript.
- Defer anything not needed for first interaction, and load heavy features only when the user reaches them.
- Break long tasks up so the browser can respond to input between chunks.
- Use CSS for animation wherever possible; transforms and opacity run off the main thread.
Third-party tags deserve particular scrutiny. Each one is code you did not write, running on your main thread, changing without your knowledge. Audit the list; most sites are carrying tags nobody has looked at in a year.
Fixing CLS: reserve the space
Layout shift is the most straightforward of the three to eliminate, and the most commonly ignored.
- Set
widthandheightattributes on every image so the browser reserves the box before the file arrives. - Reserve space for anything injected after load — banners, embeds, consent notices.
- Match fallback and webfont metrics so the swap does not reflow the paragraph.
- Never insert content above existing content once the page has rendered.
Keeping the score after launch
Performance regresses the same way it was lost the first time — one small addition at a time. The defence is automation: run a performance check in the deployment pipeline and fail the build when the budget is exceeded. A failing build is an argument that resolves itself; a monthly report is an argument that recurs.
This is how we build at X3von — the budget is part of the specification, and the pipeline enforces it. Our website development service covers the approach in more detail.