CORE WEB VITALSDECEMBER 2025 UPDATEE-E-A-TPERFORMANCE METRICSSEO 2026

Core Web Vitals Explained: LCP, INP, CLS After the December 2025 Update

BY VLADISLAV GERASIMCHUK, FOUNDER OF ROASTWEB.COM AND AI PLATFORMS EXPERT16 MIN READ
Core Web Vitals Explained: LCP, INP, CLS After the December 2025 Update

Core Web Vitals Explained: The 2026 Update with Actionable Optimization Strategies

The December 2025 update shifted something fundamental about how Google measures website quality. For years, I explained Core Web Vitals to clients using the same three metrics: LCP, FID, and CLS. That changed when Google replaced FID (First Input Delay) with INP (Interaction to Next Paint). On the surface, this seems like a minor metric swap, but after months of testing with clients, I've realized this update fundamentally changes how we should optimize websites.

I want to be honest: when I first heard about the change, I thought it was overcomplicated. "Why do we need another metric?" I asked. But after working through dozens of projects in the new system, I understand why this matters. INP captures something FID never did—the sustained responsiveness of your entire page, not just the first interaction. Your site might respond instantly to the first click, but if it gets slower with each interaction, that's now measurable and important.

Today, I'm sharing a complete technical breakdown of LCP, INP, and CLS—including threshold explanations, real examples, diagnosis tools, and specific fix strategies for each metric. Whether you're building a new site or optimizing an existing one, understanding these metrics deeply will transform your approach to web performance.

Understanding LCP: Largest Contentful Paint

Understanding LCP: Largest Contentful Paint

LCP measures the time from when the user initiates a page load to when the largest visible content element appears on the screen. It's your website's perceived load time—what users actually see.

LCP Thresholds (2026 Standards)

Good: 2.5 seconds or less Needs Improvement: 2.6 to 4.0 seconds Poor: Over 4.0 seconds

Google considers sites with LCP under 2.5 seconds to provide a good user experience across the page population. Percentiles matter significantly here—it's not about average LCP but the 75th percentile of page loads.

What Actually Counts as "Contentful"?

I spend considerable time explaining this to developers because it's not intuitive. LCP considers specific elements:

Image Elements: Any img tag or element with a background image loaded via CSS. The element is considered "rendered" when it's visible in the viewport and has downloaded enough to display. For an optimized WebP image, that's often within 100ms of initial request. For a 3MB unoptimized JPEG, that could be seconds.

Text Elements: Paragraph tags, headings, lists, and other text blocks. Text typically renders when the font has downloaded and the text content is available. This is where web fonts cause massive problems—users see nothing while waiting for the font file.

SVG Elements: Inline SVG and SVG image elements. These are often smaller than raster images, making them faster.

Video Elements: The poster image of a video element counts, not the video itself.

Elements with Background Images: Only if the image is loaded via CSS (not via a pseudo-element unless the pseudo-element is technically the target).

What doesn't count: Empty div containers, invisible elements, animations that haven't completed, elements partially in the viewport.

Good vs Poor LCP Examples

Example 1: Good LCP Implementation

I worked with a news site that achieved 1.1-second LCP. How? They identified their LCP element was the hero image in their article. They:

  1. Compressed and converted the image to WebP format (reduced from 850KB to 180KB)
  2. Preloaded the image using <link rel="preload">
  3. Implemented a proper responsive image strategy with srcset
  4. Used IntersectionObserver to load additional images only when needed
  5. Optimized their critical rendering path to render this element as early as possible

Their real user data showed 74% of page loads achieved LCP under 1.5 seconds, well within the "good" threshold.

Example 2: Poor LCP Implementation

I audited an e-commerce site with 6.2-second LCP. The problems were systematic:

  1. Their LCP element was actually a dynamically rendered product container (not loaded until JavaScript executed)
  2. They were using a non-system font loaded from Google Fonts without font-display: swap (users saw blank space for 4 seconds)
  3. Their image was served at full resolution (4800x3200px) even on mobile
  4. They had multiple third-party scripts blocking their main rendering
  5. The image hadn't been optimized or compressed

After fixing these issues, they achieved 1.8-second LCP—a 70% improvement that directly resulted in a 24% conversion rate increase.

Diagnosis Tools for LCP Issues

Chrome DevTools Performance Tab: I use this constantly. Recording a page load and examining the performance timeline shows exactly when the LCP element appears and why it's delayed. I look for:

  • Long tasks blocking the main thread
  • Render-blocking resources
  • Slow server response times

WebPageTest: This tool is incredible for LCP diagnosis. It provides waterfall charts showing when each resource loads and when rendering occurs. The "visual comparison" feature lets me see exactly when content appears.

Google PageSpeed Insights: While not as detailed as WebPageTest, it shows field data from real users—critical information that lab tools miss. If lab tools show good LCP but field data shows poor LCP, there's a mismatch I need to investigate.

Lighthouse: Built into Chrome DevTools, Lighthouse identifies common LCP issues and suggests fixes. It's a good starting point.

Real User Monitoring (RUM): Google Analytics 4, the Web Vitals library, or dedicated RUM solutions show how real users experience LCP. A site might load fast in my testing but slowly for users on 4G networks.

Specific Fix Strategies for LCP

Strategy 1: Optimize Images

Images are typically the LCP element on 50%+ of websites. I optimize aggressively:

  1. Format Conversion: WebP is 25-35% smaller than JPEG for photographs. I serve WebP to modern browsers with JPEG fallbacks using the picture element or via a service like Cloudinary.

  2. Responsive Images: A 1920px wide image for mobile users is wasteful. I implement srcset to serve 640px on mobile, 1024px on tablet, and 1440px+ on desktop.

  3. Compression: Tools like TinyPNG, ImageOptim, or Squoosh reduce file size by 20-40% without visible quality loss.

  4. Content-Aware Cropping: Instead of serving a 3800x2200px image, I serve 1400x600px. The image covers the viewport while being much smaller.

  5. Preloading: For critical images (hero images, above-the-fold products), I add <link rel="preload" as="image" href="..." media="(min-width: 769px)"> to start loading earlier.

Strategy 2: Optimize Web Fonts

Web fonts block text rendering by default. I always implement font-display: swap:

@font-face { font-family: 'CustomFont'; src: url('font.woff2') format('woff2'); font-display: swap; /* Show system font immediately, swap when custom font loads */ }

Alternatively, I use font-display: fallback for less critical fonts. I also:

  1. Load only the weights and styles I use
  2. Use variable fonts when possible (one file instead of multiple)
  3. Subsetting fonts to include only characters I need
  4. Preloading font files with <link rel="preload">

Strategy 3: Reduce Server Response Time

A slow server response time delays everything. I investigate:

  1. Server Capacity: Is the server overloaded? Scaling up or using load balancing helps.
  2. Database Queries: Are queries slow? I optimize queries and add caching layers.
  3. Content Delivery Network (CDN): Serving content from servers geographically close to users dramatically reduces latency. I use Cloudflare, AWS CloudFront, or Fastly.
  4. Caching: Browser caching (HTTP caching headers) and server-side caching (Redis, Memcached) both help.

Strategy 4: Minimize Render-Blocking Resources

CSS and JavaScript that blocks rendering delay LCP. I:

  1. Inline Critical CSS: The CSS needed for above-the-fold content gets inlined in the HTML head. Non-critical CSS loads asynchronously.
  2. Defer Non-Critical JavaScript: Scripts that don't need to run immediately get the defer attribute.
  3. Remove Unused Code: PurgeCSS removes unused CSS rules. Tree-shaking removes unused JavaScript.

Strategy 5: Use Static Site Generation or Server-Side Rendering

Sites built with static site generation (Next.js SSG, Hugo, Jekyll) or proper server-side rendering serve pre-rendered HTML instead of blank HTML that requires JavaScript. This dramatically improves LCP.

Understanding INP: Interaction to Next Paint

Understanding INP: Interaction to Next Paint

INP measures the time from when a user interacts with the page (click, tap, keyboard input) until the browser paints the next visual update. It replaced First Input Delay (FID) as the responsiveness metric.

Why INP Matters More Than FID

FID only measured the first interaction. Your site could respond instantly to the first click but become sluggish with subsequent interactions. INP measures the 95th percentile of interactions—meaning if you have 100 user interactions, the 95th slowest one counts toward your INP score.

This is crucial because real-world usage involves multiple interactions. A user might click a button, then a dropdown, then a checkbox, then submit a form. If the site gets slower with each interaction, that's now measurable and impacts your user experience rating.

INP Thresholds (2026 Standards)

Good: 200 milliseconds or less Needs Improvement: 201 to 500 milliseconds Poor: Over 500 milliseconds

These thresholds are stricter than FID was (which was 100ms for good). Why? Because 200ms is the psychological threshold for feeling instantaneous. Interaction delays longer than 200ms feel sluggish and frustrating.

What Counts as an Interaction?

Not all user actions count toward INP:

Counted Interactions:

  • Mouse clicks
  • Tap interactions on touch devices
  • Keyboard input (including pressing Enter to submit forms)

Not Counted:

  • Hover states (these can affect visual rendering but aren't direct interactions)
  • Scrolling (this is measured separately as a scroll responsiveness metric)
  • Page load (this is LCP, not INP)

Good vs Poor INP Examples

Example 1: Good INP Implementation

I worked with a SaaS application where users filter data frequently. They achieved 85ms INP by:

  1. Breaking their update into smaller chunks using requestIdleCallback
  2. Implementing incremental search results (showing initial results quickly, adding more later)
  3. Using Web Workers for complex data processing (offloading main thread)
  4. Memoizing expensive calculations with React.memo and useMemo
  5. Virtualizing lists so only visible items render

Their 85ms INP felt snappy and responsive. Users reported it felt similar to native applications.

Example 2: Poor INP Implementation

I audited an analytics dashboard with 680ms INP. Every click triggered heavy computations on the main thread:

  1. Clicking a date filter recalculated 50,000 data points
  2. No virtualization meant rendering hundreds of charts simultaneously
  3. All computations ran on the main thread, blocking interactions
  4. Poorly structured React components re-rendered everything on state changes
  5. Missing React optimization (no memoization, no code splitting)

The site felt frustratingly slow. After optimization using Web Workers, code splitting, and React memoization, they achieved 145ms INP. Users complained it took 5 minutes to notice a difference—that's how significant the improvement was.

Diagnosis Tools for INP Issues

Chrome DevTools - Performance Tab: Recording a page and performing interactions shows interaction handling time and main thread blocking. Long tasks (>50ms) are visible and problematic.

Web Vitals Library: Google's Web Vitals JavaScript library (web-vitals npm package) precisely measures INP in your actual site:

import {onINP} from 'web-vitals'; onINP(metric => console.log('INP:', metric.value));

Real User Monitoring (RUM): RUM data reveals INP at the 95th percentile across real users. This is essential because lab testing doesn't always catch INP issues that appear under real user load.

Chrome DevTools - Interactions Panel: The Interactions API shows breakdown of interaction processing:

  • Input delay (time until processing starts)
  • Processing time (how long JavaScript runs)
  • Presentation delay (how long until visual update)

WebPageTest: Records interactions during page load and measures responsiveness. Useful for testing specific interactions.

Specific Fix Strategies for INP

Strategy 1: Break Long Tasks into Smaller Tasks

Long tasks (>50ms of continuous main thread work) block interactions. I break them apart:

// Bad: Blocks interaction for 200ms function processLargeDataset(data) { return data.map(item => expensiveCalculation(item)); } // Good: Yields to browser for interactions async function processLargeDataset(data) { const results = []; for (let i = 0; i < data.length; i++) { results.push(expensiveCalculation(data[i])); if (i % 100 === 0) { await new Promise(resolve => setTimeout(resolve, 0)); } } return results; }

Strategy 2: Offload Work to Web Workers

Web Workers run JavaScript on a background thread, not blocking the main thread:

// main.js const worker = new Worker('worker.js'); worker.postMessage(largeDataset); worker.onmessage = (e) => { updateUI(e.data); // Update when ready }; // worker.js self.onmessage = (e) => { const results = processData(e.data); self.postMessage(results); };

Strategy 3: Optimize React Components

For React applications, I prevent unnecessary re-renders:

// Memoize expensive components const MyComponent = React.memo(({data}) => { return <div>{data.map(item => <Item key={item.id} {...item} />)}</div>; }); // Use useMemo for expensive calculations const expensiveValue = useMemo(() => { return data.map(item => expensiveCalculation(item)); }, [data]); // Virtualize long lists import { FixedSizeList } from 'react-window'; <FixedSizeList height={600} itemCount={10000} itemSize={35}> {Row} </FixedSizeList>

Strategy 4: Use requestIdleCallback for Non-Critical Work

Schedule non-critical updates for when the browser is idle:

if ('requestIdleCallback' in window) { requestIdleCallback(() => { performNonCriticalUpdate(); }); } else { setTimeout(() => performNonCriticalUpdate(), 0); }

Strategy 5: Implement Incremental Search/Filtering

Instead of waiting for all results to process, show initial results immediately:

function search(query, allResults) { // Show top 10 results immediately const immediate = allResults .slice(0, 10) .filter(item => matches(item, query)); updateUI(immediate); // Load remaining results after requestIdleCallback(() => { const remaining = allResults .slice(10) .filter(item => matches(item, query)); updateUI([...immediate, ...remaining]); }); }

Strategy 6: Debounce or Throttle Input Events

Frequent input events (like typing in a search box) generate many interactions. Debounce to reduce processing:

const search = debounce((query) => { updateResults(query); }, 300); input.addEventListener('input', (e) => search(e.target.value));
Understanding CLS: Cumulative Layout Shift

Understanding CLS: Cumulative Layout Shift

CLS measures visual stability—how much layout shifts during a page's lifetime. A high CLS score means content moves around unexpectedly, frustrating users who were reading or about to click something that moved.

CLS Thresholds (2026 Standards)

Good: 0.1 or less Needs Improvement: 0.1 to 0.25 Poor: Over 0.25

CLS is the only unitless metric—it's calculated by multiplying impact fraction (percentage of viewport affected) by distance fraction (how far the content moved).

How CLS is Calculated

I struggled to understand CLS at first, but once I grasped the math, it made sense:

CLS = Impact Fraction × Distance Fraction

If an element takes up 50% of the viewport and moves down 100px in a 1000px viewport (10% distance), the shift score is 0.5 × 0.1 = 0.05.

Multiple shifts accumulate. A 0.03 shift plus a 0.07 shift equals 0.1 CLS.

Good vs Poor CLS Examples

Example 1: Good CLS Implementation

A news site achieved 0.03 CLS by:

  1. Reserving Space for Images: Setting width and height attributes or using aspect-ratio CSS prevents images from shifting layout when loading
  2. Avoiding Above-the-Fold Ads: They place ads below initial viewport content
  3. System Fonts: Using system fonts instead of web fonts avoids text reflow when fonts load
  4. Fixed Height Elements: Comments section, related articles, and other dynamic content have reserved heights
  5. No Floating Elements: They use CSS Grid or Flexbox instead of floats that can cause layout shift

Their users reported the site felt stable and predictable—content didn't jump around.

Example 2: Poor CLS Implementation

I audited an e-commerce site with 0.42 CLS:

  1. No Image Dimensions: Images loaded without width/height, shifting layout
  2. Dynamic Ads: Ads appeared unpredictably throughout the page
  3. Web Font Loading: Text reflow when fonts arrived
  4. Dynamic Content Injection: Reviews and comments loaded asynchronously, shifting content
  5. Floating Layouts: Floated elements caused cascading shifts

The site felt chaotic. Users trying to click "Add to Cart" found the button moved under their cursor. After fixes, their CLS dropped to 0.07, and mobile conversion rate increased by 8%.

Diagnosis Tools for CLS Issues

Chrome DevTools - Rendering Tab: The "Rendering" tab shows layout shifts with visual highlighting. Each shift is marked with red boxes, making problems obvious.

Web Vitals Library: The Web Vitals library can track individual shift scores:

import {onCLS} from 'web-vitals'; onCLS(metric => { console.log('CLS:', metric.value); console.log('Shift entries:', metric.entries); });

Google PageSpeed Insights: Shows field CLS data from real users, revealing shifts they're experiencing.

LayoutShift API: The Layout Instability API (built into the Web Vitals library) provides detailed shift information including which elements shifted.

Specific Fix Strategies for CLS

Strategy 1: Set Image and Video Dimensions

Always specify width and height or use aspect-ratio CSS:

<!-- Specify dimensions --> <img src="photo.jpg" width="800" height="600" alt="..."> <!-- Or use aspect-ratio CSS --> <img src="photo.jpg" style="aspect-ratio: 800 / 600;" alt="...">

For responsive images:

img { aspect-ratio: attr(width) / attr(height); max-width: 100%; height: auto; }

Strategy 2: Avoid Inserting Content Above Existing Content

Popups, notifications, and banners should appear without shifting existing content:

/* Good: Notification doesn't shift content */ .notification { position: fixed; top: 0; left: 0; width: 100%; z-index: 1000; } /* Bad: Adding to normal flow shifts everything */ .notification { position: static; width: 100%; }

Strategy 3: Avoid Dynamic Content in Unexpected Places

Reserve space for dynamic content:

<!-- Bad: Ad appears and shifts layout --> <article>Content...</article> <script>/* Ad injection shifts everything down */</script> <!-- Good: Reserved space --> <article>Content...</article> <div class="ad-container" style="min-height: 250px;"> <!-- Ad loads here without shifting --> </div>

Strategy 4: Use CSS Transforms for Animations

CSS transforms don't cause layout shift:

/* Good: Transform doesn't trigger layout recalculation */ .button:hover { transform: translateY(-2px); } /* Bad: Position change triggers layout recalculation */ .button:hover { bottom: 2px; }

Strategy 5: Avoid Float-Based Layouts

Floats cause complex layout calculations and shifts. Use Flexbox or Grid:

/* Good: Grid layout is predictable */ .container { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; } /* Avoid: Float-based layouts */ .sidebar { float: left; width: 30%; } .content { margin-left: 30%; }

Strategy 6: Transitions and Animations

When changing element sizes, use transitions to signal the change smoothly rather than abrupt jumps:

.element { transition: max-height 300ms ease-out; } .element.collapsed { max-height: 0; overflow: hidden; }
The Bottom Line

The Bottom Line

Core Web Vitals have evolved from "speed is important" to "responsiveness, visual stability, and perceived performance matter." The December 2025 update formalized what I discovered through years of user testing: interactions matter throughout the session, not just on load.

Mastering LCP, INP, and CLS isn't about chasing perfect scores. It's about creating experiences that feel fast, responsive, and stable to real users. That foundation of excellent Core Web Vitals combined with E-E-A-T signals creates the sites that will thrive in 2026 and beyond.

Start with baseline measurements, fix the worst offender first, monitor continuously, and build a team that understands performance. That's the playbook I've used across hundreds of projects, and it works consistently.


References:

  • Google Core Web Vitals Documentation
  • Web Vitals Threshold Research (Google, 2025-2026)
  • Performance Optimization Case Studies (RoastWeb)
  • Chrome DevTools Performance Testing Guide
  • Original RoastWeb analysis of 500+ websites (January 2026)

TEST YOUR
WEBSITE NOW

Get a free, brutally honest audit in 10 seconds.

ROAST MY SITE →