← Back to Course Index

Module 2.1 — Core Web Vitals In Depth

Phase 2: Performance Engineering

Core Web Vitals (CWV) are Google's user-experience signals that directly influence ranking. But the word "vitals" is earned: these metrics measure real friction — slow loads, janky interactions, and visual instability — that hurt users before they ever hurt your rankings. This module treats CWV as an engineering problem, not a dashboard problem. You will learn what each metric measures at a technical level, why it fails, and how to fix it with precision.


1. Why Core Web Vitals Matter (and What They Actually Measure)

Google introduced CWV as a ranking signal in 2021 via the Page Experience update. The three current metrics are:

Each metric has three bands: Good, Needs Improvement, and Poor. Google's ranking boost applies when the majority of page views land in the "Good" band. More importantly, these metrics correlate directly with conversion rate and bounce rate — fixing them helps users and search engines simultaneously.

A critical mindset shift: do not optimize to pass a Lighthouse score. Lighthouse is a lab tool — it runs in a synthetic environment on a single machine. The signal that matters for ranking and for users is field data: real-user measurements from the Chrome User Experience Report (CrUX). You will almost always encounter a gap between lab and field. Understanding why is part of advanced CWV work.


2. LCP — Largest Contentful Paint

2.1 What It Measures

LCP measures the render time of the largest image or text block visible in the viewport, relative to when the page first started loading. It is a proxy for "when does the user perceive the page as loaded?"

Eligible LCP elements include:

2.2 The Four Root Causes of Slow LCP

Every LCP failure traces back to one or more of these four causes:

1. Slow Time to First Byte (TTFB)

The browser cannot render anything until it receives the first byte of HTML. A server that takes 1.5 seconds to respond puts you behind before the browser has parsed a single tag. TTFB is the foundation of LCP — fixing it improves everything downstream.

Fixes:

2. Render-Blocking Resources

By default, the browser pauses HTML parsing when it encounters a <link rel="stylesheet"> or a synchronous <script> in <head>. Every millisecond spent downloading and parsing these resources is a millisecond the LCP element cannot render.

Fixes:

<link rel="stylesheet" href="styles.css"
      media="print" onload="this.media='all'">

3. Slow or Improperly Loaded LCP Resource

This is the single most common LCP failure. The LCP element is an image, but the browser discovers it late because it is lazy-loaded, loaded via JavaScript, or has no preload hint.

The most impactful fix in all of CWV optimization:

<!-- WRONG: lazy-loading a hero image -->
<img src="hero.webp" loading="lazy" alt="Hero">

<!-- CORRECT: eager loading (the default) -->
<img src="hero.webp" loading="eager" fetchpriority="high" alt="Hero">
<link rel="preload" as="image"
      href="hero.webp"
      imagesrcset="hero-400.webp 400w, hero-800.webp 800w"
      imagesizes="100vw">

4. Slow Resource Load Time (Image Size and Format)

Even a correctly discovered image will be slow if it is oversized, uncompressed, or in an inefficient format.

Fixes:

2.3 Diagnosing LCP in DevTools

Open Chrome DevTools → Performance panel → record a page load. In the timeline, look for the green "LCP" marker. Click it to identify the LCP element. In the details pane you will see a breakdown:

The longest segment tells you exactly which of the four root causes to address first.


3. INP — Interaction to Next Paint

3.1 What It Measures

INP measures the latency of user interactions — how long it takes from when a user clicks, taps, or presses a key to when the browser visually responds (the next paint). It replaced First Input Delay (FID) as a Core Web Vital in March 2024. FID only measured the delay before the browser started handling the first interaction. INP measures the full duration of every interaction throughout the page's lifetime and reports the worst-case (or near-worst-case) interaction.

3.2 The Anatomy of an Interaction

Every interaction has three phases:

  1. Input delay — time from user action to when the browser event handler begins. Long tasks on the main thread cause this.
  2. Processing time — time for the event handler(s) to run.
  3. Presentation delay — time from handler completion to the next frame being painted (style recalculation, layout, paint, composite).

INP = input delay + processing time + presentation delay. Your goal is to minimize all three.

3.3 Root Causes and Fixes

Long Tasks on the Main Thread

Any JavaScript task that runs for more than 50ms is a "long task." While a long task runs, the browser cannot respond to user input — this is input delay. The most common sources:

Fixes:

// Yield control back to the browser between chunks
async function processItems(items) {
  for (const item of items) {
    processItem(item);
    // Yield after each item so the browser can handle input
    await scheduler.yield();
  }
}

Expensive Event Handlers

Even when input delay is low, processing time can be long if event handlers do too much work synchronously — for example, filtering and re-rendering a large list on every keystroke.

Fixes:

Presentation Delay / Rendering Cost

After the handler runs, the browser must recalculate styles, lay out the page, and paint. If the DOM is large or complex, this takes significant time.

Fixes:

3.4 Measuring INP in the Field

INP is a field metric only — it requires real user interactions, so Lighthouse cannot measure it accurately (it will show a simulated estimate). Use the web-vitals JavaScript library to capture real INP data from your users:

import { onINP } from 'web-vitals';

onINP(({ value, entries, attribution }) => {
  console.log('INP:', value, 'ms');
  // Send to your analytics endpoint
  sendToAnalytics({ metric: 'INP', value, attribution });
});

The attribution object tells you which element was interacted with, which phase took the longest, and which event handler ran. This is your primary debugging signal for INP in production.


4. CLS — Cumulative Layout Shift

4.1 What It Measures

CLS measures unexpected visual movement of page content during its lifetime. If a button you are about to click suddenly jumps 200px because an ad loaded above it, that is a layout shift. CLS is scored by multiplying the impact fraction (how much of the viewport moved) by the distance fraction (how far the element moved). Shifts that result from user interaction (within 500ms) are excluded.

4.2 Root Causes and Fixes

Images and Media Without Explicit Dimensions

This is the most common CLS cause. When the browser parses an <img> tag with no width and height, it allocates zero space. When the image loads, it pushes all surrounding content down.

The fix: Always set explicit width and height attributes matching the image's intrinsic dimensions. Modern browsers use these to compute aspect ratio and reserve space before the image loads:

<!-- WRONG: no dimensions, browser reserves zero space -->
<img src="product.webp" alt="Product">

<!-- CORRECT: dimensions set, browser reserves aspect-ratio space -->
<img src="product.webp" width="800" height="600"
     alt="Product" loading="lazy">

Or use the CSS aspect-ratio property for responsive images:

img {
  width: 100%;
  height: auto;
  aspect-ratio: 4 / 3; /* Reserves space before the image loads */
}

Dynamically Injected Content

Ads, banners, cookie consent bars, and chat widgets that load after the initial HTML and push content down are a primary source of CLS in the wild. Any content inserted above existing content creates a shift.

Fixes:

Web Fonts Causing FOUT/FOIT

Flash of Unstyled Text (FOUT) occurs when the browser renders text in a fallback font, then swaps to the custom web font when it loads — shifting the layout because the two fonts have different metrics (line height, character width). Flash of Invisible Text (FOIT) hides text until the font loads, which hurts LCP.

Fixes:

<link rel="preload" as="font"
      href="/fonts/inter.woff2"
      type="font/woff2" crossorigin>

Animations That Trigger Layout

CSS animations or transitions that animate properties like height, top, margin, or padding cause reflows and can contribute to CLS. Only transform and opacity animations are composited on the GPU without triggering layout.

Fix: Use transform: translateY() instead of animating top or margin-top. Use transform: scale() instead of animating width/height.

4.3 Diagnosing CLS in DevTools

In Chrome DevTools → Performance panel, enable "Layout Shift Regions" in the Rendering tab (toggle via the three-dot menu). Sections of the page that shift will be highlighted in blue when you play back the recording. Click on a layout shift entry in the Experience row of the flame chart to see the element that shifted, the score, and the timestamp.

The LayoutShift entries in the Performance timeline also show the sources — the specific DOM nodes that moved — which is the fastest path to the root cause.


5. Field Data vs Lab Data — Which to Trust

This is one of the most practically important distinctions in CWV work, and it confuses many practitioners.

Lab Data Field Data
Source Lighthouse, WebPageTest (synthetic) CrUX / PageSpeed Insights field tab / GSC CWV report
Represents One test run, one device, one network Real users on real devices and real networks
INP support Estimated only (no real interactions) Actual interactions measured
Use for Diagnosing and testing fixes in development Ranking signal; business decisions; proving improvement
Minimum data requirement None — runs instantly 28-day rolling window; needs sufficient traffic to populate

Key principle: Use lab tools (Lighthouse, DevTools, WebPageTest) to diagnose and iterate. Use field data (PageSpeed Insights field tab, Google Search Console Core Web Vitals report, your own RUM via the web-vitals library) to confirm that your fixes worked for real users and to make decisions about ranking impact.

Common reasons lab and field disagree:


6. The Critical Rendering Path — End-to-End

Understanding LCP and INP at a deep level requires understanding the browser's Critical Rendering Path (CRP). Here is the full sequence from URL to pixels:

  1. DNS lookup — resolve the domain to an IP address
  2. TCP connection + TLS handshake — establish a secure connection (significant latency on first visit)
  3. HTTP request / TTFB — request sent, first byte received
  4. HTML parse → DOM construction — the browser builds the Document Object Model as it streams HTML; parsing is blocked by synchronous scripts
  5. CSS fetch and parse → CSSOM construction — CSS is render-blocking; the browser cannot render until the CSSOM is complete
  6. JavaScript execution — parser-blocking scripts halt DOM construction; async/deferred scripts run later
  7. Render tree construction — DOM + CSSOM combined into a tree of visible elements with their computed styles
  8. Layout (Reflow) — the browser calculates the position and size of every element
  9. Paint — pixels are drawn for each element
  10. Composite — layers are combined and sent to the GPU for display

Every CWV optimization ultimately comes down to shortening one or more steps in this sequence:


7. Resource Optimization Techniques

7.1 Preconnect and Preload Hints

Resource hints tell the browser to do work early, before it would naturally discover the need.

<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="preload" as="image" href="hero.webp"
      fetchpriority="high">
<link rel="preload" as="font" href="/fonts/inter.woff2"
      type="font/woff2" crossorigin>

7.2 Image Optimization

<!-- Responsive image with WebP and AVIF, sized for viewport -->
<picture>
  <source type="image/avif"
          srcset="hero-400.avif 400w, hero-800.avif 800w, hero-1200.avif 1200w"
          sizes="(max-width: 600px) 100vw, 800px">
  <source type="image/webp"
          srcset="hero-400.webp 400w, hero-800.webp 800w, hero-1200.webp 1200w"
          sizes="(max-width: 600px) 100vw, 800px">
  <img src="hero-800.jpg"
       width="800" height="533"
       alt="Hero image"
       loading="eager"
       fetchpriority="high">
</picture>

7.3 Code Splitting and Tree Shaking

Large JavaScript bundles are the primary cause of long tasks that hurt both LCP (parse/execute during initial load) and INP (parse/execute during interaction). Modern bundlers (Webpack, Rollup, esbuild, Vite) support:

// Load the modal component only when the user opens it
button.addEventListener('click', async () => {
  const { Modal } = await import('./modal.js');
  new Modal().open();
});

7.4 Third-Party Script Management

Third-party scripts (analytics, tag managers, ads, A/B testing tools, chat widgets) are responsible for a disproportionate share of CWV failures in the wild. They run on your main thread but you do not control their code.

Strategies:


8. The Measurement Workflow

A professional CWV audit follows this sequence:

  1. Check field data first — open PageSpeed Insights for the URL. Read the field (CrUX) data, not the Lighthouse score. Note which metrics are failing and for which device category (mobile is almost always worse).
  2. Identify the failing URL groups in GSC — the Search Console Core Web Vitals report groups URLs by template type. Fixing one representative URL fixes all similar URLs.
  3. Run a WebPageTest filmstrip — filmstrip view shows you frame-by-frame exactly what the user sees and when. Identify the LCP element visually. Look for layout shifts as colored overlays.
  4. Profile in Chrome DevTools — Performance panel with CPU throttling set to 4x slowdown (simulating a mid-tier Android device). Identify: the LCP element and its load delay breakdown; long tasks blocking INP; layout shift entries and their source elements.
  5. Run the web-vitals library in production — instrument your real pages to capture actual INP, LCP, and CLS values with attribution data and send them to your analytics.
  6. Implement fixes and measure again in lab — confirm improvement in DevTools/WebPageTest before deploying to production.
  7. Wait for field data to update — CrUX is a 28-day rolling window. You will begin to see improvement in approximately 4 weeks after deploying fixes, with full field data reflection at 28 days.

9. Quick-Reference Diagnostic Checklist

LCP Checklist

INP Checklist

CLS Checklist


Module 2.1 Milestone

You are ready to progress when you can do all of the following without looking things up: