HackSec Infotech

Web

The Core Web Vitals work that actually moves the needle

We rebuilt six sites this year. The same four fixes accounted for most of the gain every time — and image budgets were not the biggest one.

We rebuilt six production sites this year with an explicit performance mandate. Going in, we expected image optimisation to dominate the wins. It did not. The same four fixes accounted for most of the improvement on every project, and only one of them involved images.

1. Eliminate render-blocking font loading

The single largest LCP improvement, on five of six sites, came from how fonts were loaded. A stylesheet request to a font CDN in the document head blocks rendering until it resolves — and on a cold 4G connection that is frequently 400–900ms before the browser has even discovered the font files it then has to fetch.

The fix is layered. Preconnect to the font origin so the TLS handshake happens in parallel. Load the stylesheet asynchronously so it never blocks the first paint. Set font-display: swap so text is visible immediately in a fallback. And — this is the part teams skip — pick a fallback stack with similar metrics so the swap does not shift your layout and wreck CLS.

Median LCP improvement across the six sites: 620ms.

2. Give every image and embed explicit dimensions

Cumulative Layout Shift is almost never caused by something subtle. It is caused by an element that has no reserved space until its content arrives. Images without width and height attributes. Ad slots. Embedded videos. Banner components that render conditionally after a client-side check.

Setting width and height attributes on every image — even when CSS overrides the actual display size — lets the browser reserve the correct aspect ratio before the bytes arrive. Combined with aspect-ratio on containers for embeds, this took four of the six sites from a failing CLS to under 0.02.

3. Stop hydrating things that were never interactive

On the two React-heavy sites, the dominant INP problem was hydration of components that had no interactivity at all — marketing copy, footers, static tables. The main thread was busy attaching event handlers to text.

Moving those to server components (or simply to plain markup) cut total blocking time by more than half. The general principle is worth stating plainly: JavaScript should be shipped for behaviour, not for templating. If a component never responds to input, it does not need to exist on the client.

4. Then, finally, images

Image work did matter — it was just not the top of the list. What produced measurable gains, in order:

  1. Correct sizing. Most oversized images were not slightly oversized; they were 2400px assets rendered at 400px.
  2. Modern formats. AVIF where supported, WebP as fallback. Typically 40–60% smaller than the JPEG for equivalent quality.
  3. Lazy loading below the fold, eager above it. Both directions matter — lazy-loading your LCP image is a common and costly mistake.
  4. fetchpriority="high" on the hero image. A one-attribute change that regularly moves LCP by 200ms.

What we stopped bothering with

Two popular optimisations produced no measurable benefit on any of the six projects. Critical CSS inlining was a wash once the stylesheet was small and cached properly, and it added real build complexity. Aggressive code splitting below about 30kB per chunk cost more in request overhead than it saved in parse time.

Measure on the device your users actually hold. A mid-range Android on a throttled connection tells you more than any lab score.

The order to work in

If you are starting today: fix fonts, reserve space for media, remove hydration from static components, then optimise images. Measure after each step with real-user data rather than a synthetic run. Four changes, in that order, got every one of our six rebuilds into the green.

Need this done, not just read?

We do this work for a living.

If any of the above sounds like a problem you have, a thirty-minute call with the engineer who wrote it is free.

  • Free 30-minute discovery call
  • NDA signed before scoping
  • You own all code and accounts