Core Web Vitals have been a ranking signal since 2021. Five years in, they remain one of the most consistently misdiagnosed areas of technical SEO — teams chase lab scores while field data stays poor, or fix LCP on desktop while mobile CLS tanks their rankings.
This checklist covers every meaningful CWV fix organized by impact. Work through it section by section. By the end, you’ll have a prioritized list of exactly what to fix on your site to hit “Good” status across all three vitals.
Before You Start: Set Up Your Measurement Stack
Fix what you can measure. Get these in place first:
- ✅ Google Search Console → Core Web Vitals report (field data by URL group)
- ✅ PageSpeed Insights for individual URL analysis (lab + field data)
- ✅ Chrome DevTools → Performance panel (detailed waterfall)
- ✅ Web Vitals Chrome extension (real-time metrics while browsing)
- ✅ CrUX dashboard (BigQuery or Looker Studio template) for trend tracking
Key rule: Fix field data (Search Console), not just lab scores (PageSpeed). If your field data is poor but lab score is 90+, the problem is your real users’ devices and connections, not what PageSpeed can see.
LCP Checklist: Largest Contentful Paint
Passing threshold: <2.5 seconds at 75th percentile of field data
Identify Your LCP Element
- ✅ Run PageSpeed Insights and note which element is flagged as LCP
- ✅ Open Chrome DevTools → Performance → record page load → find LCP marker
- ✅ Confirm LCP element on mobile (often different from desktop)
- ✅ Check if LCP element is an image, text block, or background image
LCP: Image Optimization (if LCP element is an image)
- ✅ Add
fetchpriority="high"attribute to the LCP<img>tag - ✅ Add
<link rel="preload" as="image" href="/hero.webp" fetchpriority="high">in<head> - ✅ Serve image as AVIF with WebP and JPEG fallback via
<picture>element - ✅ Compress image to under 150KB (use Squoosh, ImageOptim, or Cloudflare Images)
- ✅ Set explicit width and height attributes on
<img>tag - ✅ Serve image from CDN (Cloudflare, Fastly, AWS CloudFront)
- ✅ Cache-Control: max-age=2592000 (30 days) for image assets
- ✅ Verify LCP image is in initial HTML, not JavaScript-injected
- ✅ Remove lazy-loading from LCP image (never use
loading="lazy"on LCP element)
LCP: Server and Network Performance
- ✅ Time to First Byte (TTFB) under 200ms (affects LCP start time)
- ✅ Enable HTTP/2 or HTTP/3 on server
- ✅ Enable Gzip or Brotli compression for HTML, CSS, JS
- ✅ Preconnect to critical third-party origins:
<link rel="preconnect" href="https://fonts.googleapis.com"> - ✅ DNS prefetch for fonts, CDN, and analytics domains
- ✅ Enable server-side caching (Redis/Varnish) for dynamic pages
- ✅ Use edge computing where possible (Cloudflare Workers) to reduce geographic latency
LCP: Render Blocking Resources
- ✅ Move all non-critical CSS to load asynchronously
- ✅ Inline critical above-the-fold CSS (under 14KB) directly in
<head> - ✅ Add
deferorasyncto all non-critical JavaScript - ✅ Remove synchronous JavaScript in
<head> - ✅ Eliminate unused CSS (use PurgeCSS or Chrome Coverage tool)
- ✅ Split large JavaScript bundles; load only what’s needed for initial render
INP Checklist: Interaction to Next Paint
Passing threshold: <200ms at 75th percentile of field data
INP is the hardest vital to diagnose because it requires actual user interactions. These are the most common failure points:
INP: Identify Problem Interactions
- ✅ Install Web Vitals Chrome extension and interact with the page to surface high INP interactions
- ✅ Use Chrome DevTools → Performance → enable “Interactions” in the timeline
- ✅ Check Long Tasks panel for tasks over 50ms that block input response
- ✅ Use
PerformanceObserverwith type'interaction'in production to collect real INP data
INP: Third-Party Script Control
- ✅ Audit all third-party scripts loading on page (Tag Manager, analytics, chat, A/B tools)
- ✅ Load non-critical third-party scripts after
window.loadevent - ✅ Use
requestIdleCallbackfor scripts that don’t need immediate execution - ✅ Evaluate whether each third-party script justifies its INP cost
- ✅ Load Google Tag Manager with
asyncand minimize tags firing on click events
INP: JavaScript Optimization
- ✅ Break up long tasks with
scheduler.yield()orsetTimeout(fn, 0) - ✅ Move heavy computation to Web Workers (off main thread)
- ✅ Debounce or throttle input, scroll, and resize event handlers
- ✅ Avoid synchronous DOM queries inside event handlers (
offsetWidth,getBoundingClientRect) - ✅ Minimize DOM size (under 1,500 elements is the baseline target)
- ✅ Avoid excessive CSS selector complexity triggering expensive style recalculations
- ✅ Use CSS
content-visibility: autofor off-screen content to skip rendering
INP: React/Vue/SPA-Specific
- ✅ Wrap expensive components in
React.memoor equivalent - ✅ Use
useTransition/startTransitionfor non-urgent state updates - ✅ Virtualize long lists (react-window or react-virtualized)
- ✅ Code-split routes so initial JS bundle is minimal
- ✅ Profile component re-render frequency in React DevTools
CLS Checklist: Cumulative Layout Shift
Passing threshold: <0.1 at 75th percentile of field data
CLS: Image and Media Dimensions
- ✅ All
<img>tags have explicitwidthandheightattributes - ✅ All
<video>elements have width/height or aspect-ratio CSS set - ✅ All iframes have width/height or
aspect-ratioin CSS - ✅ CSS
aspect-ratioproperty used for responsive images:img { aspect-ratio: 16 / 9; width: 100%; }
CLS: Ad Slots and Dynamic Content
- ✅ All ad containers have a reserved minimum size before ad loads
- ✅ Cookie consent banners animate in from bottom (don’t push content down)
- ✅ Chat widgets fixed-position, don’t push page content
- ✅ Newsletter popups use overlay, not page content insertion
- ✅ Notification bars (promo bars, alert banners) included in initial render, not injected after
CLS: Font Loading
- ✅
font-display: swaporfont-display: optionalon all web fonts - ✅ Use
size-adjustCSS descriptor to match fallback font metrics to web font dimensions - ✅ Preload critical web fonts:
<link rel="preload" as="font" href="/fonts/inter.woff2" crossorigin> - ✅ Self-host fonts (eliminate third-party font service connection delay)
CLS: JavaScript-Injected Content
- ✅ No content inserted above existing page elements after load
- ✅ Lazy-loaded images use
content-visibilityor have reserved space - ✅ Accordion, tab, and expandable components reserve full expanded height in layout calculation
- ✅ Skeleton screens used for dynamic content areas (maintains layout while loading)
Field Data vs Lab Data: Closing the Gap
Your lab score (PageSpeed Insights) represents ideal conditions. Your field data (CrUX) represents real users. If you’re scoring well in lab but failing in field, the gap is usually:
- Low-end device CPU: Use Chrome DevTools with 6x CPU slowdown to simulate budget Android phones
- Slow network: Test on Fast 3G throttling for realistic mobile experience
- Cold cache: Repeat CWV testing in incognito to simulate first visits
- Geographic distance: Use WebPageTest with test agents near your lowest-performing markets
Monitoring and Ongoing Maintenance
- ✅ Set up weekly automated CWV monitoring (SpeedCurve, Calibre, or WebPageTest API)
- ✅ Alert on any page group moving from “Good” to “Needs Improvement” in Search Console
- ✅ Test CWV after every major deploy (add to CI/CD with Lighthouse CI)
- ✅ Quarterly review of third-party scripts for INP and CLS regressions
- ✅ Monitor CrUX trend monthly — seasonal traffic changes can shift field data
Our technical SEO team handles CWV remediation end-to-end — diagnosis, developer-ready fix specs, and verification against field data. We’ve fixed CWV issues on 80+ sites across every CMS and stack.
FAQ: Core Web Vitals 2026
What are the passing thresholds for Core Web Vitals in 2026?
LCP under 2.5 seconds, INP under 200ms, and CLS under 0.1 — all measured at the 75th percentile of real field data, not lab scores.
How do I check my Core Web Vitals in Search Console?
Go to Experience > Core Web Vitals in Google Search Console. You’ll see URL groups rated Good, Needs Improvement, or Poor based on real CrUX field data.
Does page speed affect Google rankings in 2026?
Yes. Core Web Vitals are a confirmed ranking signal. Pages with “Good” status across all three vitals have a measurable advantage in competitive SERPs.
What is the fastest way to improve LCP?
Add fetchpriority="high" and a preload link to your LCP image. Combined with AVIF/WebP format, this alone typically moves LCP from poor to good on most sites.
Why is my PageSpeed score 95 but Core Web Vitals still poor?
PageSpeed uses lab conditions. CWV is measured from real users. Your actual visitors on slow mobile devices experience much worse performance than the lab simulates. Fix field data, not just lab scores.