WordPress Core Web Vitals: Full Fix Checklist 2026

Fix LCP, CLS, and INP on your WordPress site — with the exact checklist I use when auditing client sites. Updated for INP (the 2024 metric change).

Dobromir Dechev
Dobromir WordPress agency owner

Quick answer

Fix WordPress Core Web Vitals by addressing LCP with image optimisation and fast hosting, CLS with explicit image dimensions, and INP by deferring non-critical JavaScript.

Core Web Vitals in 2026: what you are actually measuring

Core Web Vitals are a set of metrics Google uses as ranking signals. As of March 2024, the three active metrics are:

  • LCP (Largest Contentful Paint) - loading performance
  • CLS (Cumulative Layout Shift) - visual stability
  • INP (Interaction to Next Paint) - interactivity

INP replaced FID (First Input Delay) in March 2024. Most WordPress performance tutorials published before that date discuss FID fixes that are no longer relevant.

The thresholds for "Good" status on each metric:

MetricGoodNeeds ImprovementPoor
LCPUnder 2.5s2.5s - 4.0sOver 4.0s
CLSUnder 0.10.1 - 0.25Over 0.25
INPUnder 200ms200ms - 500msOver 500ms

Important distinction: PageSpeed Insights shows two sets of data. Lab data (Lighthouse) is a simulated test. Field data (CrUX) is real user data collected over 28 days. Google uses field data for ranking. A site can score 95 in Lighthouse while failing CWV in field data if real users are on slow devices and networks.


How to check your current Core Web Vitals status

Google Search Console (field data):

Navigate to Experience > Core Web Vitals. This shows URL-level pass/fail status based on 28 days of real user data. This is what Google uses for ranking.

URLs are grouped as: Good, Needs Improvement, or Poor. The report groups similar URLs (all product pages, all blog posts) rather than showing every URL individually.

PageSpeed Insights:

Run PageSpeed Insights on your most important pages. The "Field Data" section at the top shows CrUX data if sufficient real-user data exists for that URL. The "Lab Data" section shows Lighthouse scores.

CrUX Dashboard:

Google's free CrUX Dashboard (Looker Studio) shows historical CWV performance over time. Useful for tracking whether optimisations are improving real-user metrics.

Chrome DevTools:

For diagnosing specific issues:

  • Performance tab: record a page load, identify LCP element and timing
  • Elements tab: inspect image attributes for lazy loading and dimensions
  • Rendering tab > Core Web Vitals overlay: shows CLS shifts visually in real time

LCP (Largest Contentful Paint) - Full fix guide

LCP is the most straightforward metric to improve because it has a clear causal chain: identify the LCP element, then fix everything that delays it.

Identify the LCP element

In Chrome DevTools > Performance tab:

  1. Click the Record button
  2. Load the page
  3. Stop recording
  4. Find the "LCP" marker in the timing track
  5. The element highlighted in the summary is your LCP element

On most WordPress sites, the LCP element is either the hero image or the first heading. Hero images are more common and more actionable.

Fix TTFB first

LCP cannot be fast if the server is slow. The browser cannot start loading the LCP image until it receives the HTML. On a host with 800ms TTFB, LCP is at minimum 800ms before any other resources load.

Target: TTFB under 200ms. Achieve this with:

  • Managed hosting with server-level caching (Kinsta, Cloudways + Varnish)
  • Object caching (Redis/Memcached) for uncached dynamic requests
  • A CDN if your origin server is geographically distant from users

Handle the LCP image correctly

Never lazy-load the LCP image. Most caching plugins and performance plugins automatically add loading="lazy" to all images. This is correct for below-the-fold images but actively harmful for the LCP image.

Configure your caching plugin to exclude the LCP image from lazy loading. In WP Rocket: File Optimization > Lazy Loading > Excluded Images (CSS selector). In LiteSpeed Cache: similar exclusion option.

Or set the attribute directly in your theme template:

<img
  src="hero.webp"
  alt="Hero description"
  width="1200"
  height="600"
  loading="eager"
  fetchpriority="high"
>

Add fetchpriority="high" to the LCP image. This browser hint moves the image to the highest download priority queue. It is one of the most impactful single attributes you can add for LCP.

Preload the LCP image in the <head>:

<link rel="preload" as="image" href="/wp-content/uploads/hero.webp" fetchpriority="high">

WordPress does not add preload links automatically. Add this to your theme's functions.php or use a plugin that supports preload configuration (WP Rocket has this option).

Serve images in WebP format. WebP images are 25-40% smaller than equivalent JPEG. A 400KB JPEG hero that becomes a 240KB WebP hero shaves 150ms off the download time on a Slow 4G connection.

Serve images from a CDN. A CDN node 20ms from your user serves the LCP image 200-400ms faster than an origin server 80ms away. BunnyCDN at €1-2/month for typical traffic volumes is the cheapest option.

LCP checklist

  • [ ] Identify LCP element using Chrome DevTools Performance tab
  • [ ] TTFB is under 200ms (check in DevTools Network tab > document request > Timing)
  • [ ] Hero image has loading="eager" or no loading attribute (NOT loading="lazy")
  • [ ] Hero image has fetchpriority="high"
  • [ ] Hero image is preloaded in <head> with <link rel="preload">
  • [ ] Hero image is in WebP format
  • [ ] Hero image has explicit width and height attributes
  • [ ] Hero image is served from a CDN (check response headers for CDN-specific headers)
  • [ ] No render-blocking CSS or JavaScript between <head> and the LCP image

CLS (Cumulative Layout Shift) - Full fix guide

CLS is caused by elements that move after the initial render. The movement score is calculated based on how much of the viewport the shifted element occupied and how far it moved.

Most common CLS causes on WordPress

Images without explicit dimensions:

When the browser does not know an image's dimensions before it loads, it cannot reserve space for it. Other content renders first, then shifts down when the image loads.

<!-- Causes CLS -->
<img src="photo.jpg" alt="...">

<!-- Correct: browser reserves exact space -->
<img src="photo.jpg" alt="..." width="800" height="600">

WordPress's media library stores width and height for all uploaded images. Most themes output these attributes correctly. Where this breaks: custom shortcodes, block patterns that use inline <img> tags without dimension attributes, and some page builder elements.

Run a CLS audit: in Chrome DevTools > Rendering > Layout Shift Regions, enable the overlay. Scroll through the page and watch for red flashes indicating layout shifts.

Web fonts:

When a custom font loads and replaces the fallback, text reflows. Characters in the custom font have different widths than the fallback font, causing text blocks to change height.

Fix with font-display: swap in your @font-face declaration (or in Google Fonts URL parameter &display=swap). This tells the browser to show the fallback font immediately and swap to the custom font when ready - a visible font swap, but a layout shift only if the fonts differ significantly in character width.

For more complete elimination of font CLS: self-host fonts AND preload them, eliminating the flash entirely:

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

Cookie consent banners:

Banners that inject HTML above existing content after page load cause CLS. The entire page shifts down by the banner height.

Fix options:

  • Reserve space for the banner in the initial HTML (use a min-height container)
  • Position the banner at the bottom of the viewport (does not cause CLS)
  • Use a cookie consent solution that is part of the initial HTML render, not dynamically injected

Elementor-specific CLS:

Elementor's section layout system uses absolute positioning and JavaScript to calculate heights in some configurations. This causes layout shifts after JavaScript executes.

The most reliable fix: replace Elementor on pages where CLS is problematic. For pages that must use Elementor: disable any features that dynamically resize containers after load.

CLS checklist

  • [ ] All images have explicit width and height attributes
  • [ ] font-display: swap is set on all web fonts
  • [ ] Cookie consent banner does not inject above existing content
  • [ ] No banners or notifications inject above the fold after initial load
  • [ ] YouTube/video embeds have reserved space (aspect-ratio CSS or explicit dimensions)
  • [ ] iFrame embeds have explicit width and height
  • [ ] Ads have reserved container space (ads changing size cause CLS)

INP (Interaction to Next Paint) - Full fix guide

INP is the hardest Core Web Vitals metric to fix on WordPress sites. It measures the time from a user interaction (click, keyboard press, tap) to when the browser next paints the updated UI. The 200ms threshold is strict.

WordPress sites fail INP primarily because of main thread JavaScript blocking. When JavaScript is running, the browser cannot respond to user interactions.

Why WordPress sites fail INP

Page builders: Elementor, Divi, and WPBakery load 200-500KB of JavaScript that runs complex layout calculations on every interaction. On a mid-range Android device (the device Lighthouse simulates), this consumes the main thread for 300-800ms after a click.

WooCommerce on non-WooCommerce pages: WooCommerce loads its full JavaScript bundle on every page of the site by default, including the homepage, blog posts, and contact pages where no WooCommerce functionality exists.

Google Tag Manager: GTM loads synchronously by default and executes all tag scripts on the main thread. On a page with 5-10 tags, GTM can block the main thread for 400-600ms after page load.

Third-party chat and marketing widgets: Intercom, Drift, LiveChat, HubSpot widget, and similar tools run continuous event listeners and periodic polling on the main thread.

Fix 1: Remove WooCommerce scripts from non-WooCommerce pages

add_action( 'wp_enqueue_scripts', function() {
    if ( is_woocommerce() || is_cart() || is_checkout() || is_account_page() ) {
        return; // WooCommerce pages: load everything
    }
    // All other pages: remove WooCommerce scripts and styles
    wp_dequeue_style( 'woocommerce-general' );
    wp_dequeue_style( 'woocommerce-layout' );
    wp_dequeue_style( 'woocommerce-smallscreen' );
    wp_dequeue_script( 'woocommerce' );
    wp_dequeue_script( 'wc-cart-fragments' );
    wp_dequeue_script( 'wc-add-to-cart' );
}, 99 );

This removes WooCommerce JavaScript from pages where it serves no purpose. Impact on INP for blog posts and informational pages: significant. The wc-cart-fragments script alone runs a periodic AJAX check every 30 seconds.

Fix 2: Delay third-party scripts

WP Rocket's "Delay JavaScript Execution" feature delays specified scripts until after the first user interaction. This is the most effective way to improve INP without removing scripts entirely.

Configure WP Rocket to delay:

  • Google Tag Manager (/gtm.js)
  • Facebook Pixel
  • Analytics (if not needed before interaction)
  • Chat widget loader scripts

The trade-off: analytics may miss users who bounce without any interaction. For most sites, this is an acceptable compromise.

Fix 3: Load chat widgets on interaction only

Chat widgets (Intercom, Drift, LiveChat) are among the heaviest main-thread JavaScript burdens on WordPress sites. Loading them only when needed eliminates their impact on INP:

// Load Intercom only when user clicks the chat trigger
document.getElementById('chat-trigger').addEventListener('click', function() {
  // Load Intercom script here
  var script = document.createElement('script');
  script.src = 'https://widget.intercom.io/widget/your-app-id';
  document.head.appendChild(script);
});

Most chat platforms document this "lazy loading" approach in their developer docs.

Fix 4: Long task optimisation

In Chrome DevTools > Performance tab, long tasks (tasks taking over 50ms) are shown in red in the main thread track. Click each long task to see which JavaScript is responsible.

Common WordPress-specific long tasks:

  • Elementor editor scripts loading on the front end
  • jQuery UI being loaded by a plugin that only uses one small feature
  • A plugin loading a full image processing library for a thumbnail crop

The fix for each varies: dequeue the script conditionally, replace the plugin, or defer the script.

INP checklist

  • [ ] WooCommerce scripts dequeued on non-WooCommerce pages
  • [ ] GTM and analytics tags delayed until after first user interaction
  • [ ] Chat widget loads on interaction, not on page load
  • [ ] No page builder scripts load on pages not using the builder
  • [ ] Long tasks identified in Chrome DevTools Performance tab and attributed to specific scripts
  • [ ] jQuery loaded only on pages that need it
  • [ ] INP tested on a real mid-range Android device (not just desktop)

The audit workflow

Follow this sequence when auditing a site's Core Web Vitals:

  1. Check Google Search Console field data. Identify which pages have Poor or Needs Improvement status.
  2. Run PageSpeed Insights on a failing page. Note which metrics are failing.
  3. Run Chrome DevTools Performance recording. Identify the LCP element, long tasks, and layout shifts.
  4. Fix the highest-impact issue (usually: TTFB, LCP image loading attributes, or GTM delay).
  5. Wait 28 days. Field data in Search Console reflects a rolling 28-day window. Changes take time to show up.
  6. Verify in CrUX Dashboard that real-user metrics have improved before marking the work complete.

Do not judge fixes solely by Lighthouse scores. A Lighthouse improvement from 65 to 89 is meaningless if the field data in Search Console still shows "Poor" CLS. Real users on real devices are what the ranking signal measures.


Core Web Vitals and WordPress plugins: what breaks what

Certain plugins are known to cause or significantly worsen Core Web Vitals. This reference saves the time of diagnosing these from scratch.

LCP regression causes:

  • Elementor (when used as page builder): Elementor loads 400-600KB of CSS including styles for all element types, regardless of which elements are on the current page. On a product page using only 5 Elementor widgets, you are still loading CSS for 80+ widget types. This inflates FCP and indirectly slows LCP.

    • Fix: Use Elementor's "Improved Asset Loading" experiment (Settings > Experiments) which conditionally loads only the CSS for elements present on the current page.
  • WooCommerce (on non-shop pages): As covered in the INP section, WooCommerce loads its full script bundle everywhere. The wc-cart-fragments.min.js file makes an AJAX request on every page, which competes with LCP image loading for network bandwidth.

    • Fix: Dequeue WooCommerce scripts on non-WooCommerce pages.
  • Sliders (Slider Revolution, Swiper-based sliders): Many slider plugins use hero images as LCP candidates but apply display: none initially, then fade them in via JavaScript animation. The browser considers an image not visible if it starts with display: none, which can misidentify the actual LCP candidate and cause confusing diagnosis.

    • Fix: Use CSS opacity: 0 with a CSS animation instead of JavaScript-driven display: none toggling. Or replace sliders with static hero images (better for performance and conversion).

CLS regression causes:

  • Cookie consent plugins (Complianz, CookieYes, GDPR Cookie Consent): Many cookie consent banners inject HTML into the top of the page after load, pushing content down. The CLS score can be 0.15+ from this alone.

    • Fix: Configure the banner to appear at the bottom of the page, or use a plugin that renders the consent banner in the initial server response rather than via JavaScript injection.
  • Google Fonts via plugin: Plugins that add Google Fonts via @import in CSS rather than a <link> tag create render-blocking font loads that swap with fallback fonts and cause CLS.

    • Fix: Self-host fonts or use a plugin that loads Google Fonts via a preconnect + preload pattern.
  • Gravity Forms and similar form plugins: Forms that show validation messages above the form fields cause layout shifts when they appear. Use inline validation rather than injecting error messages above the form.

INP regression causes:

  • WP Rocket's Remove Unused CSS feature: When actively generating per-URL critical CSS, WP Rocket temporarily injects inline styles that can cause brief main-thread blocking. Disable this feature if INP is affected.

    • Fix: Disable Remove Unused CSS during peak traffic periods, or use FlyingPress which handles this more cleanly.
  • ACF (Advanced Custom Fields): ACF loads its JavaScript on the front end if you have any output that uses acf.get() or front-end forms. On pages without front-end ACF forms, this JavaScript is unnecessary.

    • Fix: Add a conditional dequeue for ACF scripts on pages without front-end forms.

Diagnosing INP issues with real user data

INP is the hardest metric to diagnose because it depends on actual user interactions, which vary by page and user behavior. Lighthouse measures TBT (Total Blocking Time), which correlates with INP but is not the same metric.

Tools for real INP diagnosis:

Chrome DevTools + Interactions pane: Chrome 110+ includes an Interactions pane in the Performance tab that records actual interaction events and their duration. To use: record a performance profile while clicking buttons, submitting forms, and navigating the page. The Interactions track shows every interaction and its processing time.

Web Vitals Chrome Extension: Provides a real-time INP overlay while browsing your site. Shows the current INP value and highlights which interaction is the worst offender. Essential for identifying which specific page element is causing INP failures.

Google Search Console: The Core Web Vitals report clusters URLs by status. Clicking a failing URL group shows which specific pages are failing INP. If a specific page type (all product pages, all checkout steps) is failing, investigate the JavaScript loaded specifically on those pages.

Interpreting INP values:

A site-wide INP of 280ms (Needs Improvement) that fails only on checkout pages indicates WooCommerce or payment gateway JavaScript is the cause. A site-wide INP failure across all pages indicates a global script (GTM, chat widget, analytics) is the cause.

Fix the narrowly-scoped issues first (they are easier to attribute and test). Then address global script issues, which require more careful testing since changes affect all pages.


The Core Web Vitals timeline: what changed and what to expect

Understanding the history helps avoid acting on outdated advice:

2020: Core Web Vitals announced as future ranking signal. LCP, FID, CLS introduced.

May 2021: Page Experience update rolls out, making CWV a ranking signal for mobile. Small ranking impact initially.

February 2022: Page Experience extends to desktop.

March 2024: INP replaces FID. FID required measuring only the delay before first interaction processing began. INP measures the full round-trip from interaction to visual update. INP is significantly stricter and harder to pass - many sites that were passing CWV with FID found they failed after the INP transition.

Ongoing: Google updates the CrUX data collection methodology periodically. Threshold values may be adjusted as broader web performance data improves.

What to watch for:

Google has signaled interest in expanding Page Experience signals. Potential future inclusions: Time to First Byte as a standalone signal (currently a diagnostic only), smooth scrolling/animation performance, and accessibility metrics.

Treat Core Web Vitals as a floor, not a ceiling. A site that barely passes all three metrics is not optimised - it has met the minimum threshold. The goal should be consistently good metrics with headroom, so that a plugin update or content addition does not immediately push a metric into the "Needs Improvement" range.


Frequently Asked Questions

What are Core Web Vitals for WordPress?
Core Web Vitals are three Google metrics: LCP (Largest Contentful Paint — how fast the main content loads, target under 2.5s), CLS (Cumulative Layout Shift — visual stability, target under 0.1), and INP (Interaction to Next Paint — responsiveness to clicks, target under 200ms).
How do I fix LCP on WordPress?
Preload your LCP image using fetchpriority='high', serve it from a CDN, use WebP format, and ensure your hosting delivers a TTFB under 200ms. WP Rocket's 'Preload Key Requests' feature handles this without server access.
How do I fix CLS on WordPress?
Set explicit width and height attributes on all images so browsers reserve space before images load. Avoid injecting content above the fold after page load. Check for web font FOUT by preloading critical fonts and using font-display: optional or swap.
What is INP and how do I improve it on WordPress?
INP replaced FID in March 2024. It measures the delay between user input (click, tap, keypress) and the next visual update. Improve it by deferring or removing non-critical JavaScript, splitting long tasks, and using a performance plugin like FlyingPress to delay third-party scripts.
Does hosting affect Core Web Vitals?
Yes, significantly. TTFB directly affects LCP — a slow server means a slow LCP no matter how optimised your images are. Switching from shared hosting to managed cloud hosting (Cloudways, Kinsta) is often the single highest-impact change for Core Web Vitals.

Was this article helpful?