Why Core Web Vitals Are a Ranking Factor
Google confirmed Core Web Vitals as a ranking signal in May 2021 and has continuously refined how they're weighted. In 2026, failing Core Web Vitals won't necessarily prevent you from ranking for low-competition keywords, but for competitive terms where other ranking factors are roughly equal, a site with good CWV will outrank one with poor CWV.
More importantly, Core Web Vitals directly impact conversion rates. Studies consistently show that improving LCP from "poor" to "good" reduces bounce rates by 20-40% and improves conversions. This is the business case that makes CWV work worth prioritizing regardless of SEO impact.
LCP Optimization (Target: < 2.5 seconds)
LCP measures how long it takes for the largest visible element in the viewport to render — typically a hero image, H1 heading, or video thumbnail.
Preload your hero image — The single highest-impact LCP optimization for image-heavy pages:
<link rel="preload" as="image" href="/hero.webp" fetchpriority="high" />
In Next.js, add priority prop to the hero <Image> component — this automatically adds the preload hint.
Use modern image formats — WebP reduces file size by 25-35% vs JPEG, AVIF by 50% vs JPEG. Use Next.js <Image> component which automatically serves WebP/AVIF with format negotiation.
Eliminate render-blocking resources — Every <script> without async or defer blocks HTML parsing. Every synchronous <link rel="stylesheet"> blocks rendering. Audit with Lighthouse and defer what you can.
Use a CDN — Geographic distance to your server adds 50-200ms of network latency. A CDN like Cloudflare or Vercel Edge serves assets from the nearest datacenter.
Lazy load below-the-fold images — Only the above-fold hero image should load immediately. Add loading="lazy" to all other images.
CLS Optimization (Target: < 0.1)
CLS measures how much the page layout shifts unexpectedly as content loads. A score of 0 is perfect; above 0.1 is "needs improvement".
Always set width and height on images — Without explicit dimensions, images take up 0px until loaded, then the layout shifts. Either use CSS aspect-ratio or HTML width/height attributes.
<img src="photo.jpg" width="800" height="600" alt="..." />
In Next.js <Image>, always provide width and height props, or use fill with a sized container.
Reserve space for ads and dynamic content — Ad slots that load asynchronously push content down if no space is reserved. Set a minimum height on the container before the ad loads.
Avoid inserting content above existing content — Cookie banners, newsletters, and notification bars that appear above content cause CLS. Use fixed positioning instead of document flow insertion.
Font loading CLS — Web fonts can cause text to reflow (FOIT/FOUT). Use font-display: swap or optional, and preload critical fonts.
INP Optimization (Target: < 200ms)
INP measures interaction responsiveness — how quickly the page reacts to user clicks, taps, and key presses.
Break up long tasks — Any main thread task over 50ms blocks input responsiveness. Use scheduler API:
// Instead of synchronous heavy computation:
function processLargeArray(items) {
for (const item of items) expensiveOperation(item);
}
// Use scheduler.postTask for non-critical work:
async function processLargeArray(items) {
for (const item of items) {
await scheduler.postTask(() => expensiveOperation(item), { priority: 'background' });
}
}
Use React concurrent features — useTransition lets you mark state updates as non-urgent, keeping the UI responsive:
const [isPending, startTransition] = useTransition();
function handleFilterChange(value: string) {
startTransition(() => {
setFilter(value); // expensive re-render deferred
});
}
Defer non-critical third-party scripts — Load analytics, chat widgets, and ad scripts after the first user interaction using the "facade" pattern — show a static placeholder until the user clicks.
Measuring in Production
web-vitals.js library — Add to your app to collect real-user measurements:
pnpm add web-vitals
import { onLCP, onCLS, onINP } from 'web-vitals';
onLCP(metric => sendToAnalytics(metric));
onCLS(metric => sendToAnalytics(metric));
onINP(metric => sendToAnalytics(metric));
GSC Core Web Vitals report — Field data from real Chrome users, aggregated by URL. Use this to prioritize which pages to optimize first.
Links: PageSpeed Insights | web.dev performance guides | Lighthouse documentation