Core Web Vitals: Fixes That Actually Move the Needle
Diagnose LCP, INP and CLS with field data, then apply the high-leverage fixes real sites need.
Everyone knows the thresholds - LCP under 2.5s, INP under 200ms, CLS under 0.1. The senior move is knowing which fix to reach for when a real page is slow, and trusting field data over a one-off Lighthouse run.
LCP is almost always one of three things
- Slow server / TTFB - cache HTML, use a CDN, cut database work on the critical path.
- The LCP resource loads late - usually the hero image or a web font. Preload it and stop lazy-loading it. Lazy-loading your largest element is the classic own-goal.
- Render-blocking CSS/JS - inline critical CSS, defer the rest.
INP is a main-thread problem
INP measures how fast the page responds to taps and clicks. It is dominated by long JavaScript tasks. Break work into smaller chunks, yield to the browser, and get third-party tags (chat widgets, A/B tools, heavy analytics) off the critical path. A single blocking 400ms handler will fail INP by itself.
CLS is about reserving space
Layout shift happens when something loads and pushes content down. Give images and embeds explicit width/height (or an aspect-ratio box), reserve space for ads and banners, and preload fonts with font-display: optional or swap so text does not reflow.
Field data (Chrome UX Report) is what Google ranks on. Lighthouse is a lab microscope for diagnosis - never celebrate a green lab score while your CrUX numbers are still red.
Example
<!-- LCP: preload the hero, do NOT lazy-load it, hint high priority -->
<link rel="preload" as="image"
href="/img/hero.avif" fetchpriority="high">
<!-- CLS: reserve exact space so nothing shifts when it decodes -->
<img src="/img/hero.avif" alt="..."
width="1200" height="630"
fetchpriority="high"> <!-- eager by default; no loading="lazy" here -->
<!-- Below-the-fold images CAN lazy-load safely -->
<img src="/img/step-2.avif" alt="..."
width="800" height="600" loading="lazy">When to use it
- A retail site diagnoses LCP at 5.1 s from field data, traces it to an unpreloaded hero image, adds fetchpriority=high and a preload hint, and cuts LCP to 1.8 s within a week.
- A publisher eliminates a CLS score of 0.28 by setting explicit width/height on all images and reserving 250px for a late-loading ad slot, reaching the 'Good' threshold of under 0.1.
- A SaaS landing page reduces INP from 450 ms to 160 ms by moving a 180 kB analytics bundle to a web worker, unblocking the main thread during user interactions.
More examples
Diagnose LCP element with DevTools
The PerformanceObserver API identifies the exact DOM element responsible for LCP, so you know whether to optimise an image, font, or server-rendered text block.
# Use the PerformanceObserver API in DevTools console to identify LCP element
# Paste in Chrome DevTools Console:
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log('LCP element:', entry.element);
console.log('LCP time (ms):', entry.startTime.toFixed(0));
console.log('LCP size (px):', entry.size);
}
}).observe({type: 'largest-contentful-paint', buffered: true});
# Reload the page — the LCP element is logged immediatelyFix image-caused LCP with preload
Combining a preload hint with fetchpriority=high on the img element eliminates the discovery delay that causes most image-based LCP failures.
<head>
<!-- Step 1: Preload the LCP image at the highest priority -->
<link rel="preload" as="image"
href="/images/hero-800.webp"
imagesrcset="/images/hero-400.webp 400w, /images/hero-800.webp 800w"
imagesizes="(max-width:600px) 400px, 800px"
fetchpriority="high">
</head>
<body>
<!-- Step 2: Mark the img itself as high priority -->
<img src="/images/hero-800.webp"
srcset="/images/hero-400.webp 400w, /images/hero-800.webp 800w"
sizes="(max-width:600px) 400px, 800px"
fetchpriority="high"
alt="Hero banner">
</body>Fix CLS from ads and dynamic content
Reserving space for ad slots and setting explicit dimensions on images prevents the layout reflow that accumulates into a high CLS score.
<style>
/* Reserve exact dimensions for ad slot before it loads */
.ad-leaderboard {
width: 728px;
min-height: 90px; /* lock height to prevent shift when ad arrives */
background: #f0f0f0;
container-type: inline-size;
}
/* Always declare width + height to prevent reflow on image load */
.product-hero {
width: 800px;
height: 600px;
object-fit: cover;
}
</style>
<div class="ad-leaderboard"></div>
<img class="product-hero" src="/shoe.webp" alt="Trail Runner Pro">
Discussion