Technical SEO MasteryCore Web Vitals, mobile-first indexing and security · Lesson 13 of 18

Core Web Vitals: diagnosis and fixes

Article · 16 min · 8 min lecture

Video lecture

Core Web Vitals: diagnosis and fixes

13 chapters · about 8 min · full transcript

Coming soon

Chapter 1 of 13

Core Web Vitals: diagnosis and fixes

  • LCP, INP, CLS and thresholds
  • Field vs lab data
  • Fixes, prioritisation, CrUX API

The narrated lecture is in production

Every chapter is scripted and ready. Browse the chapters and read the full transcript now — the video will appear here when it’s published.

Chapters

The three metrics

Core Web Vitals measure real-user experience. Google evaluates them at the 75th percentile of page loads, split by mobile and desktop.

MetricMeasuresGoodNeeds improvementPoor
LCP Largest Contentful PaintLoading: when the main content appears≤ 2.5 s≤ 4.0 s> 4.0 s
INP Interaction to Next PaintResponsiveness across all interactions≤ 200 ms≤ 500 ms> 500 ms
CLS Cumulative Layout ShiftVisual stability≤ 0.1≤ 0.25> 0.25

INP replaced First Input Delay (FID) as a Core Web Vital in March 2024. The thresholds above are unchanged as of September 2026 — beware blog posts claiming otherwise, and check web.dev's Core Web Vitals pages for the current definitions. Lesson 6.2 goes deep on INP.

Keep perspective: Google says Core Web Vitals are used by its ranking systems, but good scores do not guarantee top rankings, and relevance and helpfulness still dominate. Treat CWV as a user-experience and conversion investment with a modest search component, especially where competing pages are otherwise similar.

Field data versus lab data

  • Field data (real users): Chrome UX Report (CrUX), shown in PageSpeed Insights and the Search Console Core Web Vitals report, and your own RUM using the web-vitals JavaScript library. This is what counts.
  • Lab data (simulated): Lighthouse, PageSpeed Insights lab section, Chrome DevTools. Use it to debug, not to judge success. Lab tools cannot measure INP directly (Total Blocking Time is a rough proxy).

The Search Console report groups similar URLs, so fixes are usually template-level.

Fixing LCP

Break LCP into four parts and fix the largest one:

  1. Time to First Byte — slow servers, uncached HTML, far-away origins. Fixes: full-page caching, CDN edge caching, database and backend optimisation.
  2. Resource load delay — the browser discovers the LCP image late (for example a CSS background, or JS-inserted image).
  3. Resource load duration — the image is too heavy.
  4. Element render delay — render-blocking CSS/JS or client-side rendering delays paint.
<!-- Make the hero image discoverable and prioritised -->
<link rel="preload" as="image" href="/img/hero-1200.avif" fetchpriority="high">
<img src="/img/hero-1200.avif" width="1200" height="600" alt="Team reviewing an SEO audit"
     fetchpriority="high" decoding="async">
<!-- Never lazy-load the LCP image -->

Other LCP levers: modern formats (AVIF/WebP) with responsive srcset, inlining critical CSS, deferring non-critical JS, self-hosting key fonts, and avoiding client-side rendering for above-the-fold content.

Fixing INP

INP measures the delay from a user interaction (click, tap, key press) to the next frame painted. Poor INP almost always means long tasks on the main thread.

  • Reduce and split JavaScript; remove unused third-party scripts (chat widgets, tag managers loaded with dozens of tags, heavy A/B testing tools).
  • Break up long tasks and yield to the main thread:
async function processItems(items) {
  for (const item of items) {
    doWork(item);
    // Yield so the browser can respond to input
    if (globalThis.scheduler?.yield) await scheduler.yield();
    else await new Promise(r => setTimeout(r, 0));
  }
}
  • Give immediate visual feedback, then do heavy work afterwards.
  • Avoid large DOM sizes and forced synchronous layouts (reading layout properties right after writing styles).
  • Debounce input handlers; move heavy computation to Web Workers.

Use Chrome DevTools' Performance panel and RUM attribution (the web-vitals library's attribution build reports which element and script caused slow interactions).

Fixing CLS

  • Always set width and height (or CSS aspect-ratio) on images, videos and iframes.
  • Reserve space for ads, embeds and cookie banners; do not push content down after load.
  • Avoid inserting content above existing content, except in response to user interaction.
  • Font swaps: use font-display: optional or matched fallbacks (size-adjust) to reduce shift.
  • Animate with transform, not properties that trigger layout (top, height).
  • Check back/forward cache eligibility; pages restored from bfcache avoid many shifts.

Prioritisation workflow

  1. Open the Search Console Core Web Vitals report; list poor and needs-improvement URL groups on mobile.
  2. Map groups to templates and weigh by traffic and revenue.
  3. For each template, pull PageSpeed Insights field data and run a lab trace to find the dominant sub-part.
  4. Write tickets with the specific fix and expected metric movement.
  5. After release, wait for field data to update (CrUX uses a rolling 28-day window) before declaring success. Use the "Validate fix" button in Search Console to track.

Hands-on: pull field data with the CrUX API (Python)

The Chrome UX Report (CrUX) API returns 75th-percentile field data for an origin or URL (where there's enough traffic). Create an API key in Google Cloud, keep it in an environment variable, and:

import os, requests
KEY = os.environ["CRUX_API_KEY"]
ENDPOINT = f"https://chromeuxreport.googleapis.com/v1/records:queryRecord?key={KEY}"
METRICS = ["largest_contentful_paint", "interaction_to_next_paint", "cumulative_layout_shift"]

def p75(target, form_factor="PHONE", kind="url"):
    body = {kind: target, "formFactor": form_factor, "metrics": METRICS}
    r = requests.post(ENDPOINT, json=body, timeout=20)
    if r.status_code == 404:
        return None                               # not enough field data for this URL/origin
    r.raise_for_status()
    m = r.json()["record"]["metrics"]
    return {k: m[k]["percentiles"]["p75"] for k in METRICS if k in m}

for url in ["https://www.example.com/", "https://www.example.com/collections/cushions/"]:
    print(url, p75(url) or p75("https://www.example.com", kind="origin"))

LCP and INP come back in milliseconds and CLS as a unitless score (sometimes as a string — cast before comparing). A 404 means CrUX has insufficient data for that URL; fall back to the origin, the Search Console group, or your own RUM. The CrUX History API (records:queryHistoryRecord) returns weekly trend data for charts.

Worked example 2: a Lahore news site's CLS

A Lahore news site (illustrative) fails CLS on mobile article templates. A DevTools performance trace with Layout Shift regions shows two culprits: ad slots that collapse and expand as ads load, and a web font swap that reflows headlines. Fixes: reserve fixed-height ad containers (with a sensible fallback when no ad fills), font-display: optional plus a size-adjusted fallback font, and moving the cookie banner to a bottom overlay that doesn't push content. Field CLS moves into "good" after the 28-day window rolls over, confirmed via the CrUX API and Search Console's "Validate fix".

Common mistakes

  • Chasing a Lighthouse score of 100 while field data is fine (or vice versa).
  • Lazy-loading everything including the hero image.
  • Ignoring third-party scripts because "marketing needs them" — audit their value.

Key takeaways

  • Good thresholds at the 75th percentile: LCP ≤ 2.5 s, INP ≤ 200 ms, CLS ≤ 0.1; INP replaced FID in March 2024.
  • Judge success with field data (CrUX, Search Console, RUM); use lab tools to debug.
  • LCP: fix TTFB, discovery, weight and render delay; INP: break long tasks; CLS: reserve space.
  • CWV are a ranking input but not a shortcut to top positions; prioritise by template and business value.

Check your understanding

Quick questions to lock in the lesson. They don’t count towards your certificate.

  1. Which metric replaced First Input Delay as a Core Web Vital?
  2. The LCP element is a hero image with loading="lazy" set by a theme. Best fix?
  3. Lighthouse shows a perfect score but Search Console shows poor INP on mobile. Which should you trust for user experience?

Put it into practice

Run PageSpeed Insights on your three highest-traffic templates, record field LCP/INP/CLS, and identify the dominant cause for the worst metric on each.

Enrol for free to save your progress

Reading is always free. Enrol to keep your place, take the final assessment and earn a verifiable certificate.