Fix guides

Fix: Core Web Vitals — LCP, INP and CLS

Fix Largest Contentful Paint, Interaction to Next Paint, Cumulative Layout Shift and the Lighthouse metrics behind them, with WordPress, Shopify and code steps.

Updated Oct 24, 2026

Core Web Vitals are Google’s three user-experience metrics: LCP (how fast the main content appears), INP (how fast the page responds to clicks and taps) and CLS (how much the layout jumps). They are part of Google’s page experience signals (web.dev: Core Web Vitals). This guide fixes each metric shown in the Google Lighthouse section and the Load speed section of Keyword Score + and Web Page Audit + reports.

Find the affected metrics in the app

  1. In Audits, open a Keyword Score + or Web Page Audit + report (to start one, see Run an audit).
  2. In Google Lighthouse, switch between Mobile and Desktop. Google ranks the mobile version, so fix mobile first.
  3. Each metric shows its value and a Lighthouse score. In Load speed and Issues to fix, failing metrics are Critical (slow) or Warning (could be faster).
  4. The failed Lighthouse audits below the metrics (opportunities and diagnostics) name the files and elements that cause the problem. Use them as your to-do list. To re-test a single URL outside the app, use PageSpeed Insights or Lighthouse in Chrome DevTools.

Lighthouse names its audits slightly differently between versions. Newer versions group some audits into “insights” (for example “Render blocking requests” instead of “Eliminate render-blocking resources”, “LCP breakdown”, “Layout shift culprits”). The fixes are the same.

Largest Contentful Paint

How we detect it

Lighthouse measures when the largest image or text block in the first screen finishes rendering (largest-contentful-paint). On mobile, 2.5 s or less is good and over 4 s is poor (desktop: 1.2 s and 2.4 s). The “Largest Contentful Paint element” diagnostic (largest-contentful-paint-element) shows which element it was.

Why it matters

LCP is the moment visitors see the page is useful. It is a Core Web Vital (web.dev: LCP; how to optimize LCP). Slow LCP raises bounce rates, especially on phones.

Common causes

  • Slow server (high TTFB) → everything starts late. Check “Reduce initial server response time” and see Fix: page speed.
  • The LCP element is a large hero image → not compressed, not in WebP/AVIF, or bigger than shown.
  • The hero image is lazy-loaded → loading="lazy" on the first image delays it.
  • The image is a CSS background or inserted by JavaScript → the browser discovers it late.
  • Render-blocking CSS and JavaScript → the browser waits for files before painting.
  • Web fonts block the LCP text → fonts load late with no fallback.
  • A slider or animation on the hero → the main content appears only after scripts run.

Fix: basic

  1. Check which element is the LCP element in the Lighthouse diagnostic.
  2. If it’s an image: resize it to the largest size it is displayed at (usually 1,600 px wide or less for a full-width hero) and compress it to WebP, for example with Squoosh. Aim for under 150 KB. More in Fix: images.
  3. Turn off lazy loading for the hero image. In WP Rocket › Media, add the hero image filename to Excluded images or iframes. In LiteSpeed Cache › Page Optimization › Media Settings, add it to Lazy Load Image Excludes.
  4. Replace sliders on the first screen with one static image and a headline.
  5. Turn on page caching and a CDN (see Fix: page speed).
  6. Remove or delay third-party widgets above the fold (chat, popups, review sliders).

Fix: expert

Load the hero image early, at high priority, in the HTML (not as a CSS background):

<link rel="preload" as="image" href="/img/hero-1600.webp"
      imagesrcset="/img/hero-800.webp 800w, /img/hero-1600.webp 1600w" imagesizes="100vw" fetchpriority="high">

<img src="/img/hero-1600.webp"
     srcset="/img/hero-800.webp 800w, /img/hero-1600.webp 1600w"
     sizes="100vw" width="1600" height="900"
     fetchpriority="high" decoding="async" alt="Team at work in the office">

Never put loading="lazy" on the LCP image. Preload the font used by the LCP heading:

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

Platform steps

WordPress

  1. WP Rocket: File Optimization › Remove Unused CSS on, Load JavaScript deferred on, Delay JavaScript execution on. Media › Lazyload on, with the hero excluded. Preload › Preload fonts for the heading font.
  2. LiteSpeed Cache: Page Optimization › CSS Settings › Generate UCSS and Load CSS Asynchronously on; JS Settings › Load JS Deferred set to Deferred; Media Settings › Lazy Load Images on with the hero excluded. Image Optimization to generate WebP.
  3. Elementor: Elementor › Settings › Features (or Performance) and switch on Optimized Image Loading, which adds fetchpriority to the first image.
  4. Image plugins (ShortPixel, Imagify, EWWW) can convert existing images to WebP or AVIF in bulk.

Shopify

  1. In sections/ find the hero/image banner section. Shopify’s image_tag filter supports priority loading:
{{ section.settings.image | image_url: width: 1600 | image_tag:
   loading: 'eager', fetchpriority: 'high',
   widths: '400, 800, 1200, 1600', sizes: '100vw',
   alt: section.settings.image.alt }}
  1. Remove apps you don’t use (Shopify admin › Settings › Apps and sales channels): uninstalled apps can leave scripts in theme.liquid. Search it for leftover script tags and delete them.

Cloudflare

  1. In the Cloudflare dashboard, pick your site › Speed › Optimization › Content Optimization: turn on Early Hints.
  2. Polish (paid plans, under Speed › Optimization › Image Optimization) converts images to WebP automatically.

Verify the fix

  1. In Audits, open the + audit, click Run again and compare the LCP value on Mobile.
  2. Run the test two or three times: lab values vary a little.
  3. Real-user LCP updates in Search Console › Core Web Vitals over 28 days. Use Validate fix in that report once the change is live.

Automation

Assisted: image compression, WebP conversion and caching settings are automatable with WordPress admin access via a plugin or a Cloudflare API token; theme changes (preload, removing sliders) need theme or code access.

Interaction to Next Paint

How we detect it

INP is field data: it is measured from real visitors’ clicks, taps and key presses, so no lab test can measure it, including Lighthouse. The report shows Total Blocking Time as the lab stand-in (see below). Real INP is in Google Search Console › Core Web Vitals and in the “Discover what your real users are experiencing” panel of PageSpeed Insights. Good is 200 ms or less; poor is over 500 ms.

Why it matters

INP is a Core Web Vital (web.dev: INP; how to optimize INP). It replaced FID in March 2024. A slow INP feels like a broken page: buttons and menus don’t react.

Common causes

  • Heavy JavaScript on the main thread → long tasks block responses (see Total Blocking Time).
  • Third-party tags → chat widgets, tag managers, A/B testing and ad scripts run on every interaction.
  • Large DOM → every update re-calculates thousands of elements.
  • Expensive event handlers → a click handler that does too much work before the screen updates.

Fix: basic

  1. Open Search Console › Core Web Vitals › Mobile and click INP issue: longer than 200ms to see the affected URL groups.
  2. Remove plugins, apps and tags you don’t need. Each tag in Google Tag Manager runs code: pause tags that nobody uses.
  3. Delay non-essential scripts until the visitor interacts: WP Rocket › File Optimization › Delay JavaScript execution, or LiteSpeed Cache › Page Optimization › JS Settings › Load JS Deferred: Delayed.
  4. Replace heavy page-builder widgets (mega menus, animated counters, sliders) with simpler blocks.

Fix: expert

Split long work so the browser can paint between chunks:

<script>
  // Yield to the main thread so the click can paint first
  function yieldToMain() {
    if ('scheduler' in window && 'yield' in scheduler) return scheduler.yield();
    return new Promise(resolve => setTimeout(resolve, 0));
  }
  button.addEventListener('click', async () => {
    showSpinner();            // visible feedback first
    await yieldToMain();
    await doExpensiveWork();  // heavy work after the paint
  });
</script>

Load third-party scripts with defer or async, and debounce input handlers (search boxes, filters).

Platform steps

WordPress

  1. Use Query Monitor (plugin) or Chrome DevTools › Performance to find the slow scripts, then remove or delay the plugin behind them.

Shopify

  1. Each app adds JavaScript. In the Shopify admin go to Settings › Apps and sales channels, uninstall unused apps, then check theme.liquid and Online Store › Themes › … › Edit code › snippets for leftover app code.

Verify the fix

  1. Lab check: in Audits, Run again on the + audit and confirm Total Blocking Time went down.
  2. Field check: Search Console › Core Web Vitals › open the INP issue › Validate fix. Field data takes up to 28 days to update.

Automation

Assisted: script delay settings are automatable with WordPress admin access via a plugin; finding the slow interaction needs a person with Chrome DevTools.

Cumulative Layout Shift

How we detect it

Lighthouse adds up unexpected layout shifts while the page loads (cumulative-layout-shift). 0.1 or less is good; over 0.25 is poor. The “Avoid large layout shifts” / “Layout shift culprits” diagnostic (layout-shifts, older layout-shift-elements) lists the elements that moved, and “Image elements do not have explicit width and height” (unsized-images) lists images without dimensions.

Why it matters

CLS is a Core Web Vital (web.dev: CLS; how to optimize CLS). Content that jumps makes people tap the wrong thing and lose their place.

Common causes

  • Images, videos or iframes without width and height → space appears only when they load.
  • Cookie banners, promo bars or ads inserted at the top → everything below moves down.
  • Web fonts swapping → text re-flows when the font arrives.
  • Content injected by JavaScript above existing content (reviews widgets, “related products”).
  • Animations that change top, height or margin instead of transform.

Fix: basic

  1. Read the list of shifted elements in the Lighthouse diagnostic.
  2. Make sure every image has width and height. WordPress adds them for images inserted from the Media Library; re-insert images added by URL.
  3. Show cookie banners as an overlay at the bottom of the screen, not as a bar that pushes the page down. Most consent plugins (Complianz, CookieYes) have a Position: bottom / floating option.
  4. Give ad slots and embeds a fixed height.
  5. In WP Rocket › Media, turn on Add missing image dimensions. In LiteSpeed Cache, Page Optimization › Media Settings › Add Missing Sizes.

Fix: expert

Reserve space for every media element:

<img src="/img/team.webp" width="1200" height="800" alt="Our team" loading="lazy">

<style>
  img, video { max-width: 100%; height: auto; }
  .video-embed { aspect-ratio: 16 / 9; width: 100%; }
  .ad-slot { min-height: 250px; }
  .promo-bar { position: fixed; bottom: 0; left: 0; right: 0; }
</style>

Stop font swaps from moving text:

@font-face {
  font-family: "Inter";
  src: url("/fonts/inter.woff2") format("woff2");
  font-display: swap;
  size-adjust: 107%; /* match the fallback's size so text doesn't re-flow */
}

Animate with transform and opacity only.

Platform steps

Shopify

  1. Use the image_tag filter, which outputs width and height automatically.
  2. For announcement bars and app banners, set a fixed height in the section CSS.

Webflow

  1. Select each image and set width and height in the Style panel, or use an aspect-ratio on the wrapper.

Verify the fix

  1. In Audits, Run again on the + audit and check Cumulative Layout Shift on Mobile.
  2. Field CLS: Search Console › Core Web Vitals, then Validate fix.

Automation

Assisted: adding missing image dimensions is automatable with WordPress admin access via a caching plugin; banner and layout changes need theme access.

Total Blocking Time

How we detect it

Lighthouse adds up the time the main thread was blocked by long tasks (over 50 ms each) between first paint and interactivity (total-blocking-time). On mobile, 200 ms or less is good and over 600 ms is poor (desktop: 150 ms and 350 ms). Related diagnostics: “Reduce JavaScript execution time” (bootup-time), “Minimize main-thread work” (mainthread-work-breakdown), “Reduce unused JavaScript” (unused-javascript).

Why it matters

TBT is the lab predictor of INP. While the main thread is blocked, taps and clicks do nothing.

Common causes

  • Too many plugins, apps or tags each loading their own JavaScript.
  • Large frameworks or page builders loading all their scripts on every page.
  • Third-party embeds: chat, maps, video, social feeds, A/B testing.

Fix: basic

  1. Read the scripts listed in “Reduce JavaScript execution time”. Each row names a file and its domain, which tells you the plugin or service.
  2. Remove what you don’t need; delay the rest until interaction (WP Rocket Delay JavaScript execution, LiteSpeed Load JS Deferred: Delayed).
  3. Load scripts only on pages that use them. Perfmatters › Script Manager or Asset CleanUp (WordPress) can switch a plugin’s scripts off per page, for example a contact-form plugin everywhere except the contact page.
  4. Replace embedded maps and videos with a static preview image that loads the embed on click.

Fix: expert

<!-- Load non-critical scripts without blocking -->
<script src="/js/app.js" defer></script>
<script src="https://widget.example.com/chat.js" async></script>

<!-- Facade: load a YouTube player only on click -->
<button class="yt" data-id="VIDEO_ID" aria-label="Play video">
  <img src="https://i.ytimg.com/vi/VIDEO_ID/hqdefault.jpg" width="480" height="360" alt="">
</button>
<script>
document.querySelectorAll('.yt').forEach(b => b.addEventListener('click', () => {
  b.outerHTML = `<iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/${b.dataset.id}?autoplay=1"
    allow="autoplay; encrypted-media" allowfullscreen title="Video"></iframe>`;
}));
</script>

Code-split bundles in React or Vue so each route loads only its own code.

Platform steps

WordPress

  1. WP Rocket: File Optimization › JavaScript Files › Load JavaScript deferred and Delay JavaScript execution. Test the site afterwards; add any broken script to the exclusion list.

Shopify

  1. Uninstall unused apps and remove leftover snippets. Prefer apps that use theme app extensions (they load only where placed).

Verify the fix

  1. Run again and compare Total Blocking Time and the JavaScript diagnostics.

Automation

Assisted: script deferral is automatable with WordPress admin access via a plugin, but each change must be tested for broken features.

First Contentful Paint

How we detect it

Lighthouse measures when the first text or image is painted (first-contentful-paint). On mobile, 1.8 s or less is good and over 3 s is poor.

Why it matters

Until FCP, visitors look at a blank screen and many leave.

Common causes

  • Slow server response (TTFB).
  • Render-blocking CSS and JavaScript in the head (“Eliminate render-blocking resources” / “Render blocking requests”, render-blocking-resources).
  • Fonts that hide text while loading (font-display).

Fix: basic

  1. Turn on page caching and a CDN (see Fix: page speed).
  2. In WP Rocket turn on Optimize CSS delivery (Remove Unused CSS); in LiteSpeed Cache Load CSS Asynchronously with Generate Critical CSS.
  3. Defer JavaScript (see Total Blocking Time).

Fix: expert

<head>
  <!-- critical CSS inline, the rest async -->
  <style>/* above-the-fold CSS, a few KB */</style>
  <link rel="preload" href="/css/site.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
  <noscript><link rel="stylesheet" href="/css/site.css"></noscript>
  <script src="/js/site.js" defer></script>
</head>
@font-face { font-family: "Inter"; src: url("/fonts/inter.woff2") format("woff2"); font-display: swap; }

Platform steps

WordPress

  1. With Google Fonts loaded by the theme, host them locally: WP Rocket or OMGF (plugin) can do it, which removes a connection to another domain.

Verify the fix

  1. Run again and compare First Contentful Paint.

Automation

Automatable with WordPress admin access via a caching plugin; assisted elsewhere.

Speed Index

How we detect it

Lighthouse records how quickly the visible part of the page fills in (speed-index). On mobile, 3.4 s or less is good and over 5.8 s is poor.

Why it matters

A page that fills in gradually and early feels faster than one that stays blank and appears all at once.

Common causes

  • The same causes as FCP and LCP: slow server, render-blocking files, heavy images and late fonts.
  • Content hidden until JavaScript animations run (fade-in effects on scroll).

Fix: basic

  1. Fix FCP and LCP first.
  2. Turn off entrance animations for content in the first screen (in Elementor: select the section › Advanced › Motion Effects › Entrance Animation: None).

Fix: expert

Avoid hiding content with CSS until JavaScript adds a class (opacity: 0 on load). Render the first screen as plain HTML and CSS.

Platform steps

WordPress

  1. Remove “page preloader” or loading-spinner features from the theme options; they delay the whole page.

Verify the fix

  1. Run again and compare Speed Index.

Automation

Assisted: settings are automatable with WordPress admin access; design changes need a person.

Time to Interactive

How we detect it

Some + audits store Lighthouse’s Time to Interactive (interactive): when the page is fully ready to respond. Newer Lighthouse versions no longer score it; TBT covers the same problem.

Why it matters

Until the page is interactive, taps and clicks may do nothing.

Common causes

  • Large amounts of JavaScript downloading and running after the first paint.

Fix: basic

  1. Apply the Total Blocking Time fixes: remove, defer and delay scripts.

Fix: expert

Code-split, tree-shake and remove unused JavaScript (“Reduce unused JavaScript” lists files with the potential savings in KB).

Platform steps

WordPress

  1. Use Perfmatters › Script Manager or Asset CleanUp to unload scripts on pages that don’t use them.

Verify the fix

  1. Run again and compare the value.

Automation

Assisted: same as Total Blocking Time.

Avoid an excessive DOM size

How we detect it

Lighthouse diagnostic dom-size (“Avoid an excessive DOM size” / “Optimize DOM size”) fails when the page has a very large number of HTML elements (warning from about 800, failing from about 1,400).

Why it matters

A large DOM uses more memory, slows style calculations and makes every interaction slower, which hurts INP.

Common causes

  • Page builders nesting many wrapper elements per widget.
  • Mega menus with hundreds of links repeated for mobile and desktop.
  • Long product grids or comment lists rendered in full.

Fix: basic

  1. Simplify sections: fewer columns, inner sections and wrapper containers. In Elementor, switch on Elementor › Settings › Features › Flexbox Container and rebuild heavy sections with containers.
  2. Paginate long lists (products, comments, posts).
  3. Use one menu for mobile and desktop instead of two copies.

Fix: expert

Render long lists in pages or with content-visibility to skip off-screen rendering work:

.product-grid > .card { content-visibility: auto; contain-intrinsic-size: 400px; }

Platform steps

WordPress

  1. In your WordPress admin: Settings › Discussion (/wp-admin/options-discussion.php) › Break comments into pages with 20 top-level comments per page.

Verify the fix

  1. Run again and check the DOM size diagnostic.

Automation

Manual: reducing the DOM is a design and template change.

Common questions

Why does the report show Total Blocking Time and not INP?

INP (Interaction to Next Paint) is measured from real visitors clicking and tapping, so a lab test can't measure it. Lighthouse reports Total Blocking Time instead, which is the best lab predictor of INP. Check real INP in Google Search Console › Core Web Vitals or in the field data at the top of PageSpeed Insights.

What are the Core Web Vitals targets in 2026?

Good means LCP of 2.5 seconds or less, INP of 200 milliseconds or less and CLS of 0.1 or less, measured at the 75th percentile of real visits. FID was replaced by INP in March 2024 and is no longer used.

My Lighthouse score changes every time I run the audit. Is something wrong?

No. Lab scores vary by a few points between runs because of network and server timing. Look at the trend over several runs and at the individual metrics, and judge real performance with Search Console field data.

Which Core Web Vitals checks are in which audit?

The Google Lighthouse metrics (LCP, CLS, TBT, FCP, Speed Index, Time to Interactive) are in Keyword Score + and Web Page Audit + only. The other audits show the classic speed metrics described in Fix: page speed.

Still need help?

Ask in the chat inside the app, or send us a message on the contact page. You can also browse all help topics.