Core Web Vitals Deep-Dive
Core Web Vitals (CWV) measure real-world loading performance, responsiveness,
and visual stability. Google's position, per
https://developers.google.com/search/docs/appearance/core-web-vitals: CWV are
used by Google's core ranking systems and good CWV is recommended for success
with Search — but good scores alone do not guarantee top rankings; there
is more to page experience than CWV, and chasing a perfect score purely for
SEO is not the best use of time.
INP replaced FID on March 12, 2024. FID is gone from all Chrome tooling.
Never reference FID as a current metric.
Thresholds
Each metric is evaluated at the 75th percentile of real page loads,
segmented by mobile and desktop. A page "passes" CWV when all three metrics
are Good at p75.
| Metric |
Measures |
Good |
Needs Improvement |
Poor |
| LCP (Largest Contentful Paint) |
Loading |
≤ 2.5s |
2.5s – 4.0s |
> 4.0s |
| INP (Interaction to Next Paint) |
Responsiveness |
≤ 200ms |
200ms – 500ms |
> 500ms |
| CLS (Cumulative Layout Shift) |
Visual stability |
≤ 0.1 |
0.1 – 0.25 |
> 0.25 |
References: https://web.dev/articles/lcp · https://web.dev/articles/inp · https://web.dev/articles/cls · https://web.dev/articles/vitals
Inputs
| Input |
Required |
Notes |
| URL or origin |
Yes |
URL-level data preferred; origin-level as fallback |
| Form factor |
No |
Default: report both mobile (PHONE) and desktop |
| CrUX API key |
No |
Degrade to PSI API if absent |
Execution
Field data — CrUX API (preferred, needs an API key):
curl -s -X POST "https://chromeuxreport.googleapis.com/v1/records:queryRecord?key=$CRUX_API_KEY" \
-H 'Content-Type: application/json' \
-d '{"url": "<url>", "formFactor": "PHONE", "metrics": ["largest_contentful_paint","interaction_to_next_paint","cumulative_layout_shift"]}'
Docs: https://developer.chrome.com/docs/crux/api
- If the URL has insufficient traffic (404 response), retry with
"origin" instead of "url" and note that results are origin-level.
- No API key? Degrade gracefully to step 2 — do not fail.
Field data — PageSpeed Insights API (no key needed for light use):
curl -s "https://www.googleapis.com/pagespeedonline/v5/runPagespeed?url=<url>&category=performance&strategy=mobile"
CrUX field data is embedded in loadingExperience (URL-level) and
originLoadingExperience (origin-level): p75 percentiles and Good/NI/Poor
distributions per metric. Docs: https://developers.google.com/speed/docs/insights/v5/get-started
Lab fallback / diagnostics — Lighthouse. If no field data exists
(low-traffic page), use the lighthouseResult from step 2 or the
seo-lighthouse-audit skill. Label it clearly as lab data: lab LCP/CLS
approximate field values; lab has no INP — use TBT (Total Blocking Time)
as a rough proxy for interactivity problems, never as a substitute score.
Score each metric against the thresholds table (Good / Needs
Improvement / Poor at p75, mobile and desktop separately). Mobile is
usually worse — lead with it.
Diagnose failing metrics using the playbooks below, fetching the page
HTML (curl -s <url>) to check for concrete causes (missing
fetchpriority, unsized images, render-blocking tags, heavy third-party
scripts).
Diagnosis Playbooks
LCP — break into the four subparts
| Subpart |
Typical share |
Fixes |
| TTFB (server time to first byte) |
~40% |
CDN, edge caching, faster origin, Server-Timing header to locate backend cost, avoid redirect chains |
| Resource load delay (gap before LCP resource starts downloading) |
should be ~0 |
<link rel="preload"> the LCP image/font, fetchpriority="high" on the LCP <img>, never loading="lazy" on the LCP element, avoid CSS background-image for hero |
| Resource load time (download duration) |
varies |
Compress/resize image, modern formats (WebP/AVIF), CDN, preconnect to the resource origin |
| Element render delay (downloaded but not painted) |
should be small |
Eliminate render-blocking CSS/JS, inline critical CSS, avoid client-side rendering of the hero |
Full guide: https://web.dev/articles/optimize-lcp ·
Lighthouse LCP audit
INP — find the slow interaction
Common causes and fixes:
- Long tasks on the main thread — break work into <50ms chunks, yield with
scheduler.yield() / setTimeout; see Lighthouse TBT audit
- Heavy event handlers — debounce, move computation to Web Workers, defer non-visual work until after the next paint
- Large DOM — keep under ~1,500 nodes; big DOMs make style/layout recalc slow on every interaction (dom-size audit)
- Third-party JS — tag managers, ads, chat widgets competing for the main thread; lazy-load or facade them (third-party-summary)
- Excessive hydration (SPA frameworks) — partial/progressive hydration, server components
Full guide: https://web.dev/articles/optimize-inp
CLS — find what moved
Common causes and fixes:
- Unsized images/embeds/iframes — always set
width/height or CSS aspect-ratio so the browser reserves space
- Injected content (ads, banners, late-loading UI) — reserve slots with fixed min-height; never insert above existing content except on user interaction
- Web fonts (FOIT/FOUT swaps) —
font-display: swap plus size-adjust/fallback font metric matching; preload critical fonts (font-display audit)
- Animations using layout properties — animate
transform instead of top/left/width/height
Full guide: https://web.dev/articles/optimize-cls
Output
# Core Web Vitals Report: <url>
## Field Data (CrUX, 75th percentile, last 28 days)
| Metric | Mobile p75 | Desktop p75 | Rating (mobile) |
|--------|-----------|-------------|-----------------|
| LCP | X.Xs | X.Xs | ✅ Good / ⚠️ NI / ❌ Poor |
| INP | XXXms | XXXms | ✅/⚠️/❌ |
| CLS | 0.XX | 0.XX | ✅/⚠️/❌ |
CWV Assessment: PASS / FAIL (all three Good at p75 → pass)
Data level: URL / Origin (note if origin-level fallback was used)
## Distribution (per metric)
Good XX% | Needs Improvement XX% | Poor XX%
## Lab Data (Lighthouse — diagnostics only)
LCP X.Xs · TBT XXXms (INP proxy, not a substitute) · CLS 0.XX
## Diagnosis & Fixes (per failing metric)
### LCP (if failing): subpart breakdown + top 3 fixes
### INP (if failing): suspected cause + top 3 fixes
### CLS (if failing): shifting elements + top 3 fixes
## Context
- CWV is used by Google's ranking systems, but good CWV alone does not
guarantee rankings — see
https://developers.google.com/search/docs/appearance/core-web-vitals
- For the broader experience checklist (HTTPS, interstitials, mobile),
see the seo-page-experience skill.
1---2name: seo-core-web-vitals3description: Dedicated Core Web Vitals deep-dive: pull field data (CrUX / PageSpeed Insights), fall back to Lighthouse lab data, score LCP, INP, and CLS against Google's thresholds at the 75th percentile, and run per-metric diagnosis playbooks with concrete fixes. Use when user says "core web vitals", "CWV", "LCP", "INP", "CLS", "page speed metrics", or "field data".4---56# Core Web Vitals Deep-Dive7<!-- Updated: 2026-06-10 -->89Core Web Vitals (CWV) measure real-world loading performance, responsiveness,10and visual stability. Google's position, per11https://developers.google.com/search/docs/appearance/core-web-vitals: CWV are12used by Google's core ranking systems and good CWV is recommended for success13with Search — but good scores alone do **not** guarantee top rankings; there14is more to page experience than CWV, and chasing a perfect score purely for15SEO is not the best use of time.1617**INP replaced FID on March 12, 2024.** FID is gone from all Chrome tooling.18Never reference FID as a current metric.1920## Thresholds2122Each metric is evaluated at the **75th percentile** of real page loads,23segmented by mobile and desktop. A page "passes" CWV when all three metrics24are Good at p75.2526| Metric | Measures | Good | Needs Improvement | Poor |27|--------|----------|------|-------------------|------|28| **LCP** (Largest Contentful Paint) | Loading | ≤ 2.5s | 2.5s – 4.0s | > 4.0s |29| **INP** (Interaction to Next Paint) | Responsiveness | ≤ 200ms | 200ms – 500ms | > 500ms |30| **CLS** (Cumulative Layout Shift) | Visual stability | ≤ 0.1 | 0.1 – 0.25 | > 0.25 |3132References: https://web.dev/articles/lcp · https://web.dev/articles/inp · https://web.dev/articles/cls · https://web.dev/articles/vitals3334## Inputs3536| Input | Required | Notes |37|-------|----------|-------|38| URL or origin | Yes | URL-level data preferred; origin-level as fallback |39| Form factor | No | Default: report both mobile (PHONE) and desktop |40| CrUX API key | No | Degrade to PSI API if absent |4142## Execution43441. **Field data — CrUX API (preferred, needs an API key):**45 ```bash46 curl -s -X POST "https://chromeuxreport.googleapis.com/v1/records:queryRecord?key=$CRUX_API_KEY" \47 -H 'Content-Type: application/json' \48 -d '{"url": "<url>", "formFactor": "PHONE", "metrics": ["largest_contentful_paint","interaction_to_next_paint","cumulative_layout_shift"]}'49 ```50 Docs: https://developer.chrome.com/docs/crux/api51 - If the URL has insufficient traffic (404 response), retry with `"origin"` instead of `"url"` and note that results are origin-level.52 - **No API key? Degrade gracefully** to step 2 — do not fail.53542. **Field data — PageSpeed Insights API (no key needed for light use):**55 ```bash56 curl -s "https://www.googleapis.com/pagespeedonline/v5/runPagespeed?url=<url>&category=performance&strategy=mobile"57 ```58 CrUX field data is embedded in `loadingExperience` (URL-level) and59 `originLoadingExperience` (origin-level): p75 percentiles and Good/NI/Poor60 distributions per metric. Docs: https://developers.google.com/speed/docs/insights/v5/get-started61623. **Lab fallback / diagnostics — Lighthouse.** If no field data exists63 (low-traffic page), use the `lighthouseResult` from step 2 or the64 `seo-lighthouse-audit` skill. Label it clearly as **lab data**: lab LCP/CLS65 approximate field values; lab has no INP — use TBT (Total Blocking Time)66 as a rough proxy for interactivity problems, never as a substitute score.67684. **Score each metric** against the thresholds table (Good / Needs69 Improvement / Poor at p75, mobile and desktop separately). Mobile is70 usually worse — lead with it.71725. **Diagnose failing metrics** using the playbooks below, fetching the page73 HTML (`curl -s <url>`) to check for concrete causes (missing74 `fetchpriority`, unsized images, render-blocking tags, heavy third-party75 scripts).7677## Diagnosis Playbooks7879### LCP — break into the four subparts8081| Subpart | Typical share | Fixes |82|---------|---------------|-------|83| **TTFB** (server time to first byte) | ~40% | CDN, edge caching, faster origin, `Server-Timing` header to locate backend cost, avoid redirect chains |84| **Resource load delay** (gap before LCP resource starts downloading) | should be ~0 | `<link rel="preload">` the LCP image/font, `fetchpriority="high"` on the LCP `<img>`, never `loading="lazy"` on the LCP element, avoid CSS background-image for hero |85| **Resource load time** (download duration) | varies | Compress/resize image, modern formats (WebP/AVIF), CDN, `preconnect` to the resource origin |86| **Element render delay** (downloaded but not painted) | should be small | Eliminate render-blocking CSS/JS, inline critical CSS, avoid client-side rendering of the hero |8788Full guide: https://web.dev/articles/optimize-lcp ·89[Lighthouse LCP audit](https://developer.chrome.com/docs/lighthouse/performance/lighthouse-largest-contentful-paint)9091### INP — find the slow interaction9293Common causes and fixes:94- **Long tasks on the main thread** — break work into <50ms chunks, yield with `scheduler.yield()` / `setTimeout`; see [Lighthouse TBT audit](https://developer.chrome.com/docs/lighthouse/performance/lighthouse-total-blocking-time)95- **Heavy event handlers** — debounce, move computation to Web Workers, defer non-visual work until after the next paint96- **Large DOM** — keep under ~1,500 nodes; big DOMs make style/layout recalc slow on every interaction ([dom-size audit](https://developer.chrome.com/docs/lighthouse/performance/dom-size))97- **Third-party JS** — tag managers, ads, chat widgets competing for the main thread; lazy-load or facade them ([third-party-summary](https://developer.chrome.com/docs/lighthouse/performance/third-party-summary))98- **Excessive hydration** (SPA frameworks) — partial/progressive hydration, server components99100Full guide: https://web.dev/articles/optimize-inp101102### CLS — find what moved103104Common causes and fixes:105- **Unsized images/embeds/iframes** — always set `width`/`height` or CSS `aspect-ratio` so the browser reserves space106- **Injected content** (ads, banners, late-loading UI) — reserve slots with fixed min-height; never insert above existing content except on user interaction107- **Web fonts (FOIT/FOUT swaps)** — `font-display: swap` plus `size-adjust`/fallback font metric matching; preload critical fonts ([font-display audit](https://developer.chrome.com/docs/lighthouse/performance/font-display))108- **Animations using layout properties** — animate `transform` instead of `top/left/width/height`109110Full guide: https://web.dev/articles/optimize-cls111112## Output113114```115# Core Web Vitals Report: <url>116117## Field Data (CrUX, 75th percentile, last 28 days)118| Metric | Mobile p75 | Desktop p75 | Rating (mobile) |119|--------|-----------|-------------|-----------------|120| LCP | X.Xs | X.Xs | ✅ Good / ⚠️ NI / ❌ Poor |121| INP | XXXms | XXXms | ✅/⚠️/❌ |122| CLS | 0.XX | 0.XX | ✅/⚠️/❌ |123124CWV Assessment: PASS / FAIL (all three Good at p75 → pass)125Data level: URL / Origin (note if origin-level fallback was used)126127## Distribution (per metric)128Good XX% | Needs Improvement XX% | Poor XX%129130## Lab Data (Lighthouse — diagnostics only)131LCP X.Xs · TBT XXXms (INP proxy, not a substitute) · CLS 0.XX132133## Diagnosis & Fixes (per failing metric)134### LCP (if failing): subpart breakdown + top 3 fixes135### INP (if failing): suspected cause + top 3 fixes136### CLS (if failing): shifting elements + top 3 fixes137138## Context139- CWV is used by Google's ranking systems, but good CWV alone does not140 guarantee rankings — see141 https://developers.google.com/search/docs/appearance/core-web-vitals142- For the broader experience checklist (HTTPS, interstitials, mobile),143 see the seo-page-experience skill.144```