Most performance work we are asked to do is on a site somebody else built, usually after a Search Console email about Core Web Vitals. The temptation is to open Lighthouse, chase the score, and ship twenty changes at once. A more reliable order of work exists, and it starts by not trusting the lab number.

Field data first, lab data second

Lighthouse runs on a simulated device with a simulated network on your machine. Core Web Vitals assessments in Search Console use field data from real Chrome users. They routinely disagree. Start with the Core Web Vitals report in Search Console and the Chrome UX Report data for the origin: those tell you which URL groups are actually failing, on which device class. Lighthouse is then a debugging tool for a specific page, not the target.

Record the starting numbers for each of the three metrics before touching anything. Without a baseline you cannot tell a fix from a coincidence.

Largest Contentful Paint is almost always an image or a font

On content sites the LCP element is usually the hero image or the first heading. Work through this list in order and stop when the metric moves:

  • Identify the LCP element in the Performance panel rather than guessing.
  • If it is an image: serve modern formats, size it to the largest rendered dimensions rather than the original upload, and remove lazy loading from it — an above-the-fold image with loading=lazy is a self-inflicted delay.
  • Preload it, and preconnect to the origin serving it if that is a different host.
  • If it is text: preload the font file, use font-display: swap, and subset the font to the characters you actually use.
  • Check the server response time. If time to first byte is over about 600ms, no front-end change will rescue LCP; the fix is caching or hosting.
<link rel="preload" as="image" href="/hero-1600.avif" fetchpriority="high" />
<img src="/hero-1600.avif" width="1600" height="900" alt="..." fetchpriority="high" />

Cumulative Layout Shift is a discipline problem

Layout shift comes from content that arrives after the first paint and pushes what is already there. The causes are a short list and each has a mechanical fix: images without width and height attributes, web fonts swapping to a metrically different fallback, ad or embed slots without reserved space, and banners injected at the top of the document.

Set explicit width and height on every image so the browser reserves the box. Reserve height for embeds and ad slots with CSS rather than letting them expand on arrival. Use size-adjust on the fallback font so the swap does not reflow the paragraph. Render cookie banners and notification bars as overlays, not as elements that displace the page.

Interaction to Next Paint is main-thread work

INP measures how long the page takes to respond visually after an interaction. High INP means the main thread is busy — usually with third-party scripts, an oversized JavaScript bundle, or an expensive event handler.

  • Audit third-party tags. Marketing sites accumulate them; many are for tools nobody uses any more. Removing a tag is faster than optimising around it.
  • Load the tags that remain after consent and after interaction where the vendor supports it, rather than in the head.
  • Code-split by route so a visitor to one page does not download the whole application.
  • Break long tasks: move heavy work off the click handler, and yield to the browser before doing non-urgent updates.

Ship in small batches and verify

Deploy one category of change at a time — images, then fonts, then scripts — and let field data catch up. The Core Web Vitals assessment uses a rolling 28-day window, so a fix shipped today will not be fully reflected for four weeks. Clients need to be told this at the start, otherwise the work looks ineffective a week in.

Finally, add a guard so the site does not regress: a performance budget in CI, an alert on bundle size, and a rule that new third-party tags require a named owner and a review date. Performance is not a project you finish; it is a standard you hold.