How to Improve Core Web Vitals: LCP, INP, and CLS Quick Fixes
By Elizabeth Stein
Core Web Vitals are Google's three metrics for real-world page experience. They directly influence search rankings, and more importantly, they reflect what users actually feel when they visit your site. A page that loads slowly, responds sluggishly, or jumps around during load loses visitors - Google's data shows that as Core Web Vitals improve, bounce rate drops and conversions increase.
Here are the thresholds to target:
| Metric | Good | Needs Improvement | Poor |
|---|---|---|---|
| LCP (Largest Contentful Paint) | Under 2.5s | 2.5–4.0s | Over 4.0s |
| INP (Interaction to Next Paint) | Under 200ms | 200–500ms | Over 500ms |
| CLS (Cumulative Layout Shift) | Under 0.1 | 0.1–0.25 | Over 0.25 |
If any of these metrics are in the "needs improvement" or "poor" range, the fixes below are where to start.
Fixing LCP (Largest Contentful Paint)
LCP measures when the largest visible content element finishes rendering. It's usually your hero image, a prominent heading, or a background video. Slow LCP means users stare at a blank or half-loaded page.
1. Optimise Your Hero Image
The hero image is the LCP element on most pages. Serve it in WebP or AVIF format, resize it to the displayed dimensions, and compress it. A 3MB PNG hero is the most common LCP killer.
<img
src="/hero.webp"
alt="Product screenshot"
width="1200"
height="630"
fetchpriority="high"
/>
The fetchpriority="high" attribute tells the browser to prioritise this image over other resources.
2. Preload Critical Resources
If your LCP image is referenced in CSS (as a background image) rather than in HTML, the browser won't discover it until the CSS is parsed. Use a preload link to tell the browser about it early:
<link rel="preload" as="image" href="/hero.webp" />
3. Remove Render-Blocking CSS and JS
CSS in <head> blocks rendering until it's fully downloaded and parsed. Move non-critical CSS to the end of the body or load it asynchronously. For JavaScript, use defer or async on script tags that aren't needed for initial render.
<script src="/analytics.js" defer></script>
4. Improve Server Response Time
If your server takes 800ms to respond, you've used a third of your LCP budget before a single byte reaches the browser. Check your Time to First Byte (TTFB). Solutions include server-side caching, database query optimisation, and moving to edge hosting.
5. Use a CDN
A CDN serves content from the server closest to the user. This reduces latency by hundreds of milliseconds for users far from your origin server. Every major provider (Cloudflare, Fastly, AWS CloudFront) has a free or low-cost tier.
Fixing INP (Interaction to Next Paint)
INP measures the delay between a user interaction (click, tap, keystroke) and the next visual update. Poor INP makes your site feel unresponsive - users click a button and nothing visibly happens.
1. Break Up Long Tasks
Any JavaScript task that runs longer than 50ms blocks the main thread. During a long task, the browser can't respond to user input. Break expensive operations into smaller chunks:
// Instead of one long loop:
function processAllItems(items) {
for (const item of items) {
processItem(item); // 5ms each, 200 items = 1000ms blocked
}
}
// Yield to the browser periodically:
async function processAllItems(items) {
for (const item of items) {
processItem(item);
if (performance.now() % 50 < 5) {
await scheduler.yield(); // Let the browser handle pending input
}
}
}
2. Defer Non-Critical JavaScript
Third-party scripts (analytics, chat widgets, A/B testing) often run expensive initialisation code on page load, competing with your own code for main thread time. Load them after the page is interactive:
if (document.readyState === "complete") {
loadThirdPartyScripts();
} else {
window.addEventListener("load", loadThirdPartyScripts);
}
3. Optimise Event Handlers
If a click handler triggers a synchronous computation before showing feedback, INP will suffer. Show an immediate visual response (loading spinner, optimistic UI update) and do the heavy work asynchronously.
4. Use Web Workers
Move CPU-intensive operations (data parsing, encryption, image processing) off the main thread entirely using Web Workers. The main thread stays free to respond to input.
const worker = new Worker("/heavy-computation.js");
worker.postMessage(data);
worker.onmessage = (e) => updateUI(e.data);
5. Reduce Main Thread Work
Audit your JavaScript bundles. Remove unused dependencies, tree-shake your imports, and consider whether you need that 200KB charting library on every page. Smaller bundles mean less parsing and execution time.
Fixing CLS (Cumulative Layout Shift)
CLS measures unexpected layout movement. When elements shift around after the page starts rendering, users lose their place, miss click targets, or accidentally tap the wrong thing.
1. Set Image and Video Dimensions
When the browser encounters an <img> without width and height, it doesn't know how much space to reserve. The image loads, the browser recalculates layout, and everything below shifts down.
<img src="/photo.webp" alt="Team photo" width="800" height="600" />
For responsive images, use the aspect-ratio CSS property:
img {
width: 100%;
height: auto;
aspect-ratio: 4 / 3;
}
2. Reserve Space for Ads and Embeds
Third-party ads and embeds (YouTube, Twitter, maps) are the most common CLS culprits on content sites. Reserve their exact dimensions in your layout before they load:
.ad-slot {
min-height: 250px;
width: 300px;
}
3. Don't Inject Content Above the Fold
Banners, cookie consent bars, and notification bars that appear at the top of the page after initial render push everything down. Either render them server-side so they're part of the initial layout, or position them as fixed/sticky overlays that don't affect document flow.
4. Use CSS Containment
The contain property tells the browser that an element's layout is independent of the rest of the page. This limits how far layout changes propagate:
.card {
contain: layout;
}
5. Handle Font Loading
When a web font loads and replaces the fallback font, text can reflow and shift surrounding content. Use font-display: swap and size your fallback font to match your web font's metrics:
@font-face {
font-family: "CustomFont";
src: url("/fonts/custom.woff2") format("woff2");
font-display: swap;
size-adjust: 105%;
}
Preload your primary font to reduce the swap delay:
<link rel="preload" as="font" href="/fonts/custom.woff2" type="font/woff2" crossorigin />
Measuring Your Progress
Chrome DevTools: The Performance panel shows LCP, CLS, and INP for individual page loads. Use it during development to catch regressions before they ship.
PageSpeed Insights: Provides both lab data (simulated) and field data (real users from CrUX). The field data is what Google uses for rankings - optimise for that, not the lab score.
Rocket Vitals: Measures Core Web Vitals across every page of your site using real Chromium rendering with Chrome DevTools Protocol. You get per-page scores, not just a homepage snapshot, so you can find the specific pages dragging down your site-wide metrics.
Related Checks
Rocket Vitals measures each Core Web Vital per page. See the full details for each check:
- Slow Largest Contentful Paint - LCP exceeds 4 seconds
- Slow First Contentful Paint - FCP exceeds 3 seconds
- Poor Cumulative Layout Shift - CLS exceeds 0.25
- Render-blocking CSS - Too many render-blocking stylesheets
- Render-blocking JS - Scripts blocking page render
- No preload hints - Critical resources not preloaded
Run a free scan to see which Core Web Vitals issues affect your site and get page-by-page breakdowns. Scan your site →