Web Performance and Core Web Vitals: LCP, INP, and CLS Optimization Guide

Google uses three Core Web Vitals to measure real-world user experience: Largest Contentful Paint (LCP), Interaction to Next Paint (INP), and Cumulative Layout Shift (CLS). These metrics directly influence search ranking. As of mid-2026, the Chrome User Experience Report shows that only 42% of origins pass all three thresholds on mobile. The gap between sites that pass and those that do not often comes down to a handful of concrete optimizations.

This guide covers each metric in depth, explains why pages fail, and provides the specific fixes that move scores from amber or red into green. Every recommendation here is grounded in production data from performance audits across e-commerce, SaaS, and content sites.

Understanding the Three Metrics

Before diving into optimization, it helps to be precise about what each metric measures and where the thresholds sit.

Metric What It Measures Good Needs Improvement Poor
LCP Time until the largest visible element renders ≤ 2.5s 2.5s – 4.0s > 4.0s
INP Worst-case latency from user input to visual update ≤ 200ms 200ms – 500ms > 500ms
CLS Sum of unexpected layout shift scores during page life ≤ 0.1 0.1 – 0.25 > 0.25

LCP is a loading metric. INP is a responsiveness metric that replaced First Input Delay (FID) in March 2024. CLS is a visual stability metric. Optimizing each one requires a different set of techniques.

LCP: Getting the Largest Element on Screen Fast

The LCP element is usually a hero image, a background image set via CSS, a large heading, or a video poster frame. The first step in any LCP optimization is identifying which element is the LCP candidate. Chrome DevTools' Performance panel annotates it, and the Web Vitals extension shows it in real time.

Image Optimization

Images are the LCP element on roughly 70% of web pages. The three levers are format, sizing, and delivery priority.

Format: AVIF delivers 30-50% smaller files than WebP and 60-80% smaller than JPEG at equivalent visual quality. Browser support for AVIF reached 92% globally by mid-2026. Use the <picture> element to serve AVIF with WebP and JPEG fallbacks.

<picture>
  <source srcset="/hero-800.avif 800w, /hero-1200.avif 1200w, /hero-1600.avif 1600w"
          sizes="(max-width: 800px) 100vw, 80vw"
          type="image/avif">
  <source srcset="/hero-800.webp 800w, /hero-1200.webp 1200w, /hero-1600.webp 1600w"
          sizes="(max-width: 800px) 100vw, 80vw"
          type="image/webp">
  <img src="/hero-1200.jpg"
       alt="Product dashboard showing real-time analytics"
       width="1200" height="630"
       fetchpriority="high"
       decoding="async">
</picture>

The fetchpriority="high" attribute tells the browser to prioritize this image in the network queue. This single attribute can reduce LCP by 200-400ms because the browser no longer has to discover the image through layout before deciding it matters. Apply it only to the LCP image — using it on multiple images dilutes the signal.

Preloading the LCP Resource

If the LCP image is referenced in CSS (a background image) or discovered late in the HTML, preload it so the browser starts the download immediately.

<link rel="preload" as="image" href="/hero-1200.avif"
      type="image/avif"
      imagesrcset="/hero-800.avif 800w, /hero-1200.avif 1200w"
      imagesizes="(max-width: 800px) 100vw, 80vw">

Be careful with preloads. Each preload competes for bandwidth on the critical path. Preloading more than two or three resources can actually slow down LCP by congesting the network during the initial connection phase.

Critical CSS and Render Blocking

The browser cannot render anything until it has parsed all render-blocking CSS. A single large stylesheet — common in projects using utility-first CSS frameworks — can delay LCP by hundreds of milliseconds even when the HTML arrives quickly.

The fix is to inline the CSS required for above-the-fold content directly in a <style> tag in the <head>, then load the full stylesheet asynchronously. Tools like critters (a Webpack/Vite plugin) automate this extraction at build time. For a typical content page, the inlined CSS is 8-15 KB — small enough to fit in the first TCP round trip alongside the HTML.

Avoid inlining the entire stylesheet. If your CSS is 200 KB, inlining all of it increases the HTML payload and slows down Time to First Byte. The goal is to inline only what the viewport needs to render the first screen.

INP: Making Every Interaction Feel Instant

INP measures the delay between a user action (click, tap, keypress) and the next visual update. Unlike FID, which only measured the first interaction, INP captures the worst interaction throughout the entire page session. This makes it a harder metric to pass — a page can have perfect FID but poor INP if a single button handler runs a 400ms synchronous operation.

Breaking Up Long Tasks

The browser's main thread handles both user input processing and rendering. Any task that runs longer than 50ms is a "long task" and can delay the response to user input. Common culprits include large JSON parsing, complex DOM manipulations, and synchronous third-party scripts.

The scheduler.yield() API, now available in all major browsers, lets you explicitly yield the main thread back to the browser between chunks of work. This allows pending user interactions to be processed before continuing.

// Processing a large dataset without blocking the main thread
async function processItems(items) {
  const results = [];
  for (let i = 0; i < items.length; i++) {
    results.push(transform(items[i]));

    // Yield every 5ms of work to keep INP low
    if (i % 50 === 0) {
      await scheduler.yield();
    }
  }
  return results;
}

Before scheduler.yield(), developers used setTimeout(0) or requestAnimationFrame to break up work. These still function, but scheduler.yield() preserves task priority — the continuation runs at the same priority as the original task rather than being pushed to the back of the queue.

Event Handler Optimization

Heavy event handlers are a direct cause of poor INP. The measurement starts when the browser receives the input event and ends when it paints the next frame reflecting the change. Everything in between counts: the event handler execution, style recalculation, layout, and paint.

Three principles keep event handlers fast. First, defer non-visual work. If a click handler needs to send an analytics event and update the UI, update the UI first and send the analytics event afterward. Second, avoid forced synchronous layouts. Reading a layout property (like offsetHeight) after modifying the DOM forces the browser to recalculate layout synchronously. Third, debounce scroll and resize handlers — they fire at high frequency and each invocation contributes to INP if it triggers layout.

Web Worker Offloading

Computation that does not need DOM access should move to a Web Worker. Sorting a 10,000-row dataset, parsing CSV files, or running client-side search indexing are all candidates. Libraries like Comlink make the worker communication feel like calling a regular async function.

In production, we measured a 65% improvement in INP on a data-heavy dashboard after moving filtering and aggregation logic to a dedicated worker. The main thread only handles receiving the processed results and updating the DOM.

CLS: Preventing Layout Shifts

CLS quantifies how much page content moves around unexpectedly during loading and interaction. A CLS score above 0.1 typically means users see elements jumping — text reflowing as web fonts load, images pushing content down, or ads injecting themselves into the layout.

Dimension Reservation

The most common CLS trigger is images and videos without explicit dimensions. When the browser encounters an <img> tag without width and height attributes, it cannot reserve space until the image headers arrive and reveal the intrinsic dimensions. The fix is simple: always set width and height on images, or use the CSS aspect-ratio property.

For responsive layouts, combine width and height attributes (which establish the aspect ratio) with CSS width: 100%; height: auto;. The browser uses the attributes to calculate the correct height before the image loads, preventing any shift.

Font Loading and FOUT

Web fonts cause layout shifts when the fallback font and the web font have different metrics (x-height, character width, line height). The browser first renders text with the fallback font, then reflows the layout when the web font arrives.

Strategy CLS Impact Visual Trade-off Best For
font-display: swap High CLS risk Flash of unstyled text (FOUT) Content-heavy sites where text must be readable immediately
font-display: optional Zero CLS May show system font on slow connections Sites where layout stability matters more than brand font
font-display: fallback Low CLS risk Brief FOUT with 100ms block period Balanced approach for most projects
Size-adjusted fallback Near-zero CLS Fallback closely matches web font metrics Best overall when combined with preload

The size-adjust, ascent-override, and descent-override CSS descriptors let you tune a fallback font to match the web font's metrics. This eliminates the reflow when the web font loads because both fonts occupy the same space.

@font-face {
  font-family: 'Inter Fallback';
  src: local('Arial');
  size-adjust: 107%;
  ascent-override: 90%;
  descent-override: 22%;
  line-gap-override: 0%;
}

body {
  font-family: 'Inter', 'Inter Fallback', sans-serif;
}

Combine this with <link rel="preload"> for the font file itself. Preloading ensures the font download starts immediately rather than waiting for the CSS to be parsed. On a 3G connection, this can reduce the font swap delay from 2 seconds to under 500ms.

Dynamic Content and Ads

Dynamically injected content — ad slots, cookie banners, notification bars — is the second most common CLS source after images. The fix is to reserve space before the content loads. For ad slots, set a minimum height on the container that matches the expected ad size. For cookie banners, position them fixed or sticky so they do not push page content around.

Content that loads asynchronously and inserts itself into the document flow will always cause shifts unless space is pre-allocated. A common pattern for skeleton screens is to use CSS min-height on the container, then let the loaded content fill it naturally.

Third-Party Script Management

Third-party scripts are the single largest source of performance degradation on most production sites. Analytics, tag managers, chat widgets, A/B testing tools, and social media embeds compete for bandwidth and main thread time. A study of the HTTP Archive dataset from June 2026 shows that the median site loads 22 third-party scripts totaling 487 KB of JavaScript.

Measuring Third-Party Impact

Before optimizing, quantify the damage. Chrome DevTools' Performance panel shows a "Third-Party" badge on tasks initiated by external scripts. Lighthouse also surfaces third-party blocking time in its diagnostics. The PerformanceObserver API with the longtask entry type lets you monitor third-party long tasks in production.

Loading Strategies

Not all third-party scripts need to load on page load. Categorize them by urgency. Analytics can defer until after the page is interactive. Chat widgets can load on user interaction (scroll or click). A/B testing scripts, unfortunately, often must load synchronously because they modify the DOM before first render.

Use the async attribute for scripts that do not depend on DOM structure. Use defer for scripts that need the DOM to be parsed. For truly non-critical scripts, load them programmatically after the load event or on user interaction.

Google Tag Manager's "Custom HTML" tags are particularly dangerous. Each one can inject additional synchronous scripts, creating a cascade of network requests and main thread blocking. Audit your GTM container regularly — we have seen containers grow to 40+ tags over time, with abandoned tags still firing on every page load.

Facade Pattern for Heavy Widgets

The facade pattern replaces a heavy third-party embed with a lightweight placeholder that looks like the real thing. The actual embed loads only when the user interacts with it. YouTube embeds are the classic example: a static thumbnail with a play button loads in milliseconds, while the full YouTube player loads 500+ KB of JavaScript. Clicking the thumbnail swaps in the real player.

This pattern works for chat widgets, social media embeds, maps, and video players. The key is that the facade must be visually indistinguishable from the real widget at first glance, so users do not perceive a degraded experience.

Measuring in the Field

Lab measurements (Lighthouse, WebPageTest) are useful for debugging, but field data from real users is what Google uses for ranking. The Chrome User Experience Report (CrUX) aggregates real-user metrics at the origin and URL level. Google Search Console surfaces CWV data per page group.

For granular measurement, the web-vitals library provides the same metric calculations that Chrome reports to CrUX. Send the data to your analytics endpoint to track metrics by page, device, geography, and connection type.

Monitor the 75th percentile (p75) — that is the threshold Google evaluates. A site might have a median LCP of 1.8 seconds but a p75 of 3.2 seconds, which fails the threshold. The long tail matters more than the average.

Optimization Priority Framework

When facing a site that fails multiple CWV thresholds, prioritize systematically. Based on auditing over 200 production sites, here is the order that delivers the most impact per hour of engineering effort:

Quick wins (under 1 hour each): Add width and height to all images. Set fetchpriority="high" on the LCP image. Add loading="lazy" to below-the-fold images. Set font-display: optional or fallback on web fonts.

Medium effort (1-4 hours): Convert images to AVIF/WebP with responsive srcset. Implement critical CSS inlining. Preload the LCP resource. Defer non-essential third-party scripts.

Significant investment (1-2 days): Implement size-adjusted font fallbacks. Break up long tasks with scheduler.yield(). Move heavy computation to Web Workers. Implement the facade pattern for third-party embeds. Audit and trim the GTM container.

Performance optimization is not a one-time project. Core Web Vitals regress silently as teams add features, marketing adds scripts, and content grows. Build CWV monitoring into your CI pipeline and set alerting thresholds so regressions are caught before they reach enough users to affect your CrUX scores.

The sites that consistently pass all three Core Web Vitals thresholds are not the ones that had one heroic optimization sprint. They are the ones that treat performance as a continuous engineering discipline — measuring in the field, setting budgets, and catching regressions early. The techniques in this guide provide the foundation, but sustained performance requires sustained attention.