Fix Core Web Vitals

Improve LCP, INP, and CLS — step-by-step fixes for slow websites (includes tools for minification and image optimization)

Core Web Vitals (2024–2025 thresholds)

MetricGoodNeeds ImprovementPoor
LCP
Largest Contentful Paint
≤ 2.5s2.5–4s> 4s
INP
Interaction to Next Paint (replaced FID March 2024)
≤ 200ms200–500ms> 500ms
CLS
Cumulative Layout Shift
≤ 0.10.1–0.25> 0.25

Test your site: PageSpeed Insightsweb.dev/measure • Chrome DevTools → Lighthouse panel

LCP — Largest Contentful Paint

Measures when the largest content element (usually image, video, or large text block) becomes visible. Poor LCP means slow loading hero image, render-blocking CSS/JS, or slow server response.

LCP Server response time (TTFB) is slow

TTFB (Time to First Byte) > 600ms hurts LCP. Often caused by slow backend, unoptimized database queries, or no caching.

  1. 1Enable server-side caching (Redis, Varnish, CDN edge cache).
  2. 2Optimize database queries — add indexes, reduce N+1 queries.
  3. 3Use a CDN (Cloudflare, Fastly, Vercel edge) to serve static assets closer to users.
  4. 4Upgrade hosting if shared hosting is the bottleneck.
💡 TTFB goal: under 200ms for fast sites. Use webpagetest.org to break down TTFB. Check server logs for slow endpoints.
LCP Render-blocking CSS or JavaScript

Browser can't paint LCP element until blocking CSS/JS loads and executes. Critical CSS is inline or deferred; non-critical CSS loads async.

  1. 1Inline critical CSS (above-the-fold styles) directly in <head>.
<style>
/* critical CSS: navbar, hero bg */
</style>
  1. 2Load non-critical CSS asynchronously:
<link rel="preload" as="style" href="non-critical.css"
onload="this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="non-critical.css"></noscript>
  1. 3Defer non-critical JavaScript: defer or async attribute.
<script src="analytics.js" defer></script>
💡 Use our CSS Minifier to reduce CSS file size. For JS, use JS Minifier.
LCP Large, unoptimized hero image / video

LCP element is often a large image or video. If not optimized, it takes too long to download and render.

  1. 1Compress and resize to exact dimensions needed. Use modern formats:
<picture>
<source srcset="hero.avif" type="image/avif">
<source srcset="hero.webp" type="image/webp">
<img src="hero.jpg" width="1200" height="600" alt="..." loading="eager">
</picture>
  1. 2Dimensions: don't rely on CSS scaling. Ship an image close to its display size.
  1. 3Preload LCP image:
<link rel="preload" as="image" href="hero.webp" imagesrcset="hero-400.webp 400w, hero-800.webp 800w, hero-1200.webp 1200w" imagesizes="100vw">
  1. 4Use loading="eager" for LCP image (default for above-the-fold), loading="lazy" for below-fold.
💡 Use our Image Resizer to resize and optimize images. GIF Optimizer for animated content.

Target widths for responsive: 400, 800, 1200, 1600, 2000px. WebP is ~30% smaller than JPEG; AVIF is even smaller but less compatible.
LCP No preconnect or DNS prefetch for critical origins

Third-party resources (CDNs, fonts, APIs) add DNS lookup, TCP, TLS handshake delays. preconnect warms up connections early.

<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="dns-prefetch" href="https://cdn.example.com">
💡 Preconnect to critical third parties: Google Fonts, CDNs, analytics (if you must use them). Don't overuse — each connection costs CPU/memory. crossorigin needed for fonts to avoid CORS preflight.
INP — Interaction to Next Paint (replaced FID)

Measures responsiveness: time from user interaction (click, tap, keypress) to next paint. Poor INP means UI feels laggy. Often caused by long JavaScript tasks blocking the main thread.

INP Long main-thread tasks block responsiveness

JavaScript running > 50ms delays paint after interaction. Break up heavy work or move off-main-thread.

  1. 1Break large tasks into chunks with setTimeout or requestIdleCallback:
function processChunk(items) {
const chunk = items.splice(0, 50); // 50 items per chunk
chunk.forEach(processItem);
if (items.length) {
setTimeout(() => processChunk(items), 0);
}
}
  1. 2Move expensive work to a Web Worker:
// main.js
const worker = new Worker('worker.js');
worker.postMessage(data);
worker.onmessage = (e) => { /* result */ };
// worker.js (runs off main thread)
self.onmessage = (e) => {
const result = heavyCalc(e.data);
self.postMessage(result);
};
  1. 3Debounce or throttle frequent event handlers (scroll, resize, mousemove).
💡 Use async/await with setTimeout or requestAnimationFrame to yield to the browser. Chrome DevTools → Performance panel identifies longest tasks.
INP JavaScript bundle too large

Large JS downloads, parses, and executes slowly, delaying interaction handlers. Code-split, tree-shake, and minify.

  1. 1Code-split: load only what's needed for current page.
// Dynamic import (React.lazy, Vue async components, etc.)
import('./heavy-module.js').then(module => {
module.init();
});
  1. 2Minify all JS — use our JS Minifier or build tool (Terser, esbuild).
  1. 3Lazy-load non-critical scripts with defer/async or after DOMContentLoaded.
  1. 4Use requestIdleCallback to schedule low-priority work during idle time.
⚠️ Don't block the main thread. Any single task > 50ms risks poor INP. If a function takes 200ms, split it into four 50ms chunks with setTimeout or queueMicrotask.
CLS — Cumulative Layout Shift

Measures visual stability. Elements moving around unexpectedly cause poor CLS. Usually images without dimensions, dynamic content insertion, or fonts that shift text.

CLS Images without width/height attributes

Browser doesn't know image aspect ratio until it loads → layout shifts when image renders.

<!-- ❌ Bad — no dimensions -->
<img src="photo.jpg" alt="...">
<!-- ✅ Good — explicit width & height -->
<img src="photo.jpg" width="800" height="600" alt="...">
<!-- For responsive: use CSS aspect-ratio (modern) -->
<img src="photo.jpg" style="aspect-ratio: 4/3; width: 100%; height: auto;">
💡 If you don't know exact dimensions, compute aspect ratio. Example: 1200×600 → aspect-ratio: 2/1. Modern CSS: aspect-ratio. Legacy: padding-bottom hack.
CLS Dynamic content inserted above existing elements

Banners, notifications, or ads that appear after page load push content down. Reserve space in advance.

  1. 1Reserve fixed-height container for dynamic content:
<div id="banner-container" style="min-height: 80px;"></div>
  1. 2Or inject content off-screen (fixed/absolute positioning) then animate in without affecting flow.
  1. 3For ads, use aspect-ratio to reserve ad slot dimensions before network response.
💡 CLS whitelist: user-initiated interactions (clicks, scrolls) don't count. But auto-playing videos, cookie banners, or notification popups that appear immediately count toward CLS.
CLS Font loading causes FOIT / FOUT layout shift

Web fonts load after system font fallback. Switch causes text size/width to shift (FOUT — flash of unstyled text) or invisible (FOIT — flash of invisible text).

  1. 1Use font-display: swap in @font-face (default in Google Fonts since 2020):
@font-face {
font-family: 'MyFont';
src: url('myfont.woff2') format('woff2');
font-display: swap;
}
  1. 2Preload key fonts to avoid late discovery:
<link rel="preload" as="font" href="/fonts/inter.woff2" type="font/woff2" crossorigin>
  1. 3For hero text, consider system font stack to avoid any FOIT/FOUT.
💡 System fonts: font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;. Zero CLS from font loading. Accept tradeoff of brand consistency.
OmniTools That Help Core Web Vitals

About This Tool

The Core Web Vitals fix guide provides actionable solutions for LCP, INP, and CLS optimization. Each section explains the problem, offers step-by-step code fixes, and links to relevant OmniTools (CSS/JS Minifiers, Image Resizer). Improve your PageSpeed score and user experience with these proven techniques.

Quick Start Checklist

⚡ LCP (2.5s target)

Optimize hero image (resize, WebP/AVIF, preload), inline critical CSS, defer non-critical JS, enable CDN, reduce TTFB with caching.

🎯 INP (200ms target)

Break long JS tasks (>50ms) with setTimeout/worker. Debounce events. Minify and code-split. Avoid synchronous layout (layout thrashing).

📏 CLS (0.1 target)

Set explicit width/height on images, use aspect-ratio CSS, reserve space for ads/banners, font-display: swap, avoid injecting content above fold after load.

🛠️ OmniTools to use

CSS Minifier, JS Minifier, Image Resizer, GIF Optimizer. All free, client-side, zero upload.

Frequently Asked Questions

Why did FID change to INP?

Google replaced First Input Delay (FID) with Interaction to Next Paint (INP) in March 2024 because INP captures the full interaction latency — not just the delay until first input is processed, but until the page visually responds. FID only measured the first interaction; INP measures all interactions (click, tap, keypress) and reports worst (or p75). More representative of real user experience.

How do I test Core Web Vitals locally?

Use Chrome DevTools: open Lighthouse panel, select "Performance" category, check "Core Web Vitals" metrics. Or use the PageSpeed Insights API. For field data, use the Chrome User Experience Report (CrUX) or Web Vitals Chrome extension. Testing locally on localhost gives lab data; real-user data requires public URL.

Will minification really help?

Yes — smaller JS/CSS means faster download, parse, and compile. For a site with 500KB of JS, minification might reduce to 300KB (40% smaller). Combined with gzip/brotli compression on server, transfer size drops further. Each 100KB reduction can shave ~0.5–1s off LCP on 3G. But focus on LCP blockers first: images and render-blocking resources.

Is this tool free?

Yes, completely free with no sign-up required.

Related Tools