Blog · Performance & Optimization
How to Improve Core Web Vitals in 2026: LCP, INP & CLS
A practical guide to the three Core Web Vitals — what LCP, INP and CLS measure, the exact 2026 thresholds, and how to fix each one — with the reasoning and code for every technique.
Core Web Vitals are Google’s three field metrics for real-world user experience — and a confirmed ranking signal. They are Largest Contentful Paint (LCP) for loading, Interaction to Next Paint (INP) for responsiveness, and Cumulative Layout Shift (CLS) for visual stability. This guide covers the exact 2026 thresholds and concrete fixes for each, with the reasoning and a code example.
1. Know the exact thresholds and how they're scored
Each metric has three bands. LCP: good ≤ 2.5s, needs improvement ≤ 4s, poor > 4s. INP: good ≤ 200ms, needs improvement ≤ 500ms, poor > 500ms. CLS: good ≤ 0.1, needs improvement ≤ 0.25, poor > 0.25. Scores are the 75th percentile of real visits (so you’re optimizing the experience most users get, not the average), and all three must be "good" to pass.
// targets to hit at the 75th percentile of real users
LCP ≤ 2.5s (loading)
INP ≤ 200ms (responsiveness — replaced FID in 2024)
CLS ≤ 0.1 (visual stability)
2. Measure field data, not just lab scores
Lighthouse gives a lab estimate on one simulated device; Core Web Vitals are scored on *real users* (field data) via the Chrome UX Report. They often disagree — INP in particular can only be measured from real interactions. Track field data with the web-vitals library sending to your analytics, and read PageSpeed Insights’ field section and Search Console’s Core Web Vitals report.
import { onLCP, onINP, onCLS } from "web-vitals";
function send(m) { navigator.sendBeacon("/vitals", JSON.stringify(m)); }
onLCP(send); onINP(send); onCLS(send); // real-user field data
3. Optimize the LCP element
LCP is the render time of the largest element in the viewport — usually a hero image or a big heading. Identify it (DevTools marks it), then make it load fast: serve a modern format at the right size, don’t lazy-load it, and raise its priority with fetchpriority="high". Google measured LCP dropping from 2.6s to 1.9s from fetch priority alone.
<img src="hero.avif" fetchpriority="high"
width="1200" height="675" alt="…">
<!-- never add loading="lazy" to the LCP image -->
4. Cut render-blocking CSS and JavaScript
LCP can’t happen until the browser has processed render-blocking resources in the critical path. Inline the critical CSS and defer the rest, and load scripts with defer/async so parsing isn’t blocked. Every render-blocking request you remove from the critical path pulls LCP earlier.
<style>/* critical above-the-fold CSS inlined */</style>
<link rel="preload" href="/main.css" as="style" onload="this.rel='stylesheet'">
<script src="/app.js" defer></script>
5. Improve Time to First Byte
LCP can’t beat the time it takes to get the first byte of HTML. A slow server, no caching, or a far-away origin inflates TTFB and caps every downstream metric. Serve from a CDN/edge close to users, cache HTML where you can, and use preconnect to warm connections to critical third-party origins.
<link rel="preconnect" href="https://cdn.example.com" crossorigin>
<link rel="dns-prefetch" href="https://cdn.example.com">
<!-- and serve HTML from an edge/CDN with caching -->
6. Fix INP by breaking up long tasks
INP measures the latency from a user interaction to the next paint. The main cause of a poor INP is long JavaScript tasks blocking the main thread when the user clicks or types. Break work into chunks that yield to the browser (scheduler.yield(), feature-detected for Safari), and defer non-urgent work so the response paints quickly.
button.onclick = async () => {
updateUINow(); // paint the response first
if (scheduler?.yield) await scheduler.yield();
doHeavyFollowUpWork(); // then the expensive part
};
7. Minimize main-thread JavaScript
Less JavaScript means less parsing, compiling and executing on the main thread — which improves both LCP (the thread is free to render) and INP (it’s free to respond). Code-split, tree-shake, remove unused dependencies, and move heavy computation to a Web Worker. Shipping less JS is the most reliable lever on both metrics.
# find and cut main-thread weight
npx source-map-explorer dist/assets/*.js
npx knip # unused files/deps/exports
8. Reserve space for media to fix CLS
CLS measures unexpected layout shifts. The most common cause is images, videos and ads without reserved space — content jumps down when they load. Always set width/height (or CSS aspect-ratio) so the browser reserves the box up front. This single habit eliminates most CLS.
<img src="photo.jpg" width="1600" height="900" alt="…"
style="width:100%;height:auto">
/* or with CSS */
img { aspect-ratio: 16 / 9; width: 100%; height: auto; }
9. Never inject content above existing content
Banners, cookie notices, ads and "you might also like" strips inserted above content already on screen push everything down — a jarring shift and a CLS penalty. Reserve space for them ahead of time, insert them below the fold, or overlay them (fixed/absolute) so they don’t reflow the page.
/* reserve the slot so filling it shifts nothing */
.ad-slot { min-height: 250px; }
/* or take the banner out of flow */
.cookie-bar { position: fixed; bottom: 0; }
10. Preload fonts and use font-display: swap
Web fonts cause both invisible text (hurting LCP if the LCP element is text) and a shift when the fallback swaps to the web font (hurting CLS). Preload the one critical font, use font-display: swap to show fallback text immediately, and size-match the fallback (size-adjust, ascent-override) so the swap barely moves anything.
<link rel="preload" href="/fonts/inter.woff2" as="font" type="font/woff2" crossorigin>
@font-face {
font-family: "Inter"; src: url("/fonts/inter.woff2") format("woff2");
font-display: swap; size-adjust: 100%;
}
11. Keep interactions off the critical path with transitions
When an interaction triggers an expensive re-render (filtering a big list, switching views), mark it as non-urgent so the browser paints the immediate feedback first. In React, useTransition/useDeferredValue do this; in vanilla JS, update the visible state synchronously and schedule the heavy work after a yield.
// React: keep the click responsive, defer the heavy update
const [isPending, startTransition] = useTransition();
onClick={() => { setActive(id); startTransition(() => setHeavyView(id)); }}
12. Prioritize critical resources
Help the browser fetch the right things first. fetchpriority="high" on the LCP image, preload for a late-discovered critical resource, and preconnect for critical third-party origins all shorten the path to LCP. Use them surgically — over-preloading steals bandwidth from the very resource you’re trying to speed up.
<link rel="preload" as="image" href="hero.avif" fetchpriority="high">
<link rel="preconnect" href="https://api.example.com" crossorigin>
13. Monitor continuously in CI and in the field
Core Web Vitals regress with everyday changes — a new hero image, an added script, a font swap. Catch it two ways: a Lighthouse CI check on pull requests for lab regressions, and real-user monitoring (the web-vitals library) for field truth. Watch the 75th percentile, since that’s what Google scores.
# Lighthouse CI on every PR (lab guardrail)
npx @lhci/cli autorun
# plus web-vitals RUM for the 75th-percentile field data
Core Web Vitals checklist
- Targets (75th percentile): LCP ≤ 2.5s, INP ≤ 200ms, CLS ≤ 0.1 — pass all three.
- Measure real-user field data (
web-vitals, CrUX), not just Lighthouse. - LCP: optimize the LCP element, cut render-blocking resources, improve TTFB.
- INP: break up long tasks, minimize main-thread JS, defer non-urgent work.
- CLS: set image/media dimensions, don’t inject content above the fold, tame font swap.
- Prioritize critical resources with
fetchpriority/preload/preconnect. - Monitor continuously — Lighthouse CI plus real-user monitoring.
Frequently asked questions
What are the Core Web Vitals thresholds in 2026?
Why do my Lighthouse and real-user scores differ?
web-vitals library and PageSpeed Insights’ field section.How do I improve INP specifically?
scheduler.yield(), feature-detected for Safari), minimize main-thread JS, paint immediate feedback before doing heavy follow-up work, and defer non-urgent updates (React useTransition). Then validate with real-user INP data.Do Core Web Vitals affect SEO?
Prototype fixes and preview them instantly in XCODX Studio. Dig into each metric with our Image Optimization Guide (LCP/CLS), How to Speed Up JavaScript (INP) and the Modern Web Performance Checklist.