WordPress

Optimising WooCommerce Performance: LCP, INP and CLS for Your Shop

7 May 2026 · 10 min read


WooCommerce shops aren't ordinary WordPress pages — which is why generic performance guides often fall short. A WooCommerce shop has more JavaScript (mini-cart, variation selection, AJAX add-to-cart), more external resources (payment widgets, review badges, tracking pixels) and cache exceptions that don't exist on standard WordPress: page caching is automatically disabled for logged-in users and for any session with a filled cart.

The result: many shops that look good in a PageSpeed test on the homepage perform poorly on product and checkout pages — exactly where customers buy.

In this article we go through the three Core Web Vitals that matter most for WooCommerce shops: LCP, INP and CLS. For each metric we show the typical WooCommerce problem, a quick win you can implement immediately, and a deeper fix worth considering for developers.

LCP on WooCommerce

The problem

The LCP element on product pages is almost always the product image. But WooCommerce treats it like any other image — it's lazy-loaded, has no fetchpriority attribute, and there's no preload link in the <head>. That means the browser only discovers the page's most important image late, and only then starts loading it.

On shop homepages, a hero slider is often the LCP element. Sliders load several images at once — but only one is visible at any moment. The browser wastes bandwidth on images that aren't currently shown, while the visible LCP image waits.

Quick win

  • No lazy loading for the first product image: never mark the main image on product pages with loading="lazy". Many themes set this globally — check in the browser devtools whether loading="lazy" is on the product image.
  • Enable WebP: if your image optimisation plugin (Imagify, ShortPixel, Smush) offers WebP conversion, turn it on. Product images benefit especially because they're often large.
  • Simplify or replace the hero slider: if the slider is mainly there for conversion styling but is killing performance, a static hero image is the better choice for LCP.

Dev tip

Give the LCP product image fetchpriority="high" and no loading="lazy". This works via the woocommerce_single_product_image_thumbnail_html filter, or directly in the theme template single-product/product-image.php:

add_filter('woocommerce_single_product_image_thumbnail_html', function ($html) {
    // Only for the first/active image
    $html = str_replace('loading="lazy"', 'loading="eager"', $html);
    $html = str_replace('<img ', '<img fetchpriority="high" ', $html);
    return $html;
}, 10, 1);

Also add a preload link for the LCP image in the <head> — ideally with the specific image URL for that product:

add_action('wp_head', function () {
    if (!is_product()) return;
    global $product;
    $image_id  = $product->get_image_id();
    $image_url = wp_get_attachment_image_url($image_id, 'woocommerce_single');
    if ($image_url) {
        echo '<link rel="preload" as="image" href="' . esc_url($image_url) . '">' . "\n";
    }
}, 1);

INP on WooCommerce

The problem

INP (Interaction to Next Paint) measures how quickly a page responds to user interactions. WooCommerce is an INP risk for several reasons:

Add-to-cart button: a click triggers an AJAX request, updates the mini-cart in the header, and fires event handlers in several plugins. All on the main thread. On slow connections or with many active plugins, this feels noticeably sluggish.

Variation selection: if a product has multiple variations (colour, size), WooCommerce synchronously calculates which combination is available on every selection, and updates price, image and stock in the DOM. With many variations, this is one of the heaviest operations on the page.

Cart fragments: by default WooCommerce asynchronously refreshes the mini-cart content via AJAX after every page load (wc-cart-fragments.js). That's unnecessary JavaScript overhead on every page — even when there's no mini-cart present.

Global scripts: WooCommerce and many WooCommerce plugins load JavaScript globally — even on pages with no shop context (blog, about us, contact).

Quick win

  • Deactivate cart fragments if you don't use a mini-cart: many themes don't have a mini-cart in the header at all. Yet wc-cart-fragments.js still runs on every page. Check and deactivate if needed:
add_action('wp_enqueue_scripts', function () {
    if (is_admin()) return;
    wp_dequeue_script('wc-cart-fragments');
}, 11);
  • Load WooCommerce scripts only on shop pages: with a plugin such as "WooCommerce Cart Fragments Deactivation", or manually via wp_dequeue_script on non-shop pages.

Dev tip

Load WooCommerce scripts specifically only where they're needed:

add_action('wp_enqueue_scripts', function () {
    if (is_admin()) return;
    if (!is_woocommerce() && !is_cart() && !is_checkout()) {
        // Remove WooCommerce scripts on non-shop pages
        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');
    }
}, 99);

For the mini-cart: fragment caching instead of live AJAX. WooCommerce offers woocommerce_add_to_cart_fragments as a hook to cache the mini-cart's HTML output. Transients or an object cache (Redis) help speed up the AJAX response significantly.

CLS on WooCommerce

The problem

CLS (Cumulative Layout Shift) measures unexpected layout shifts. WooCommerce shops have several typical CLS sources:

Product images without an aspect ratio: if width and height aren't set on the <img> tag and no aspect-ratio is set via CSS, the browser doesn't reserve space before loading. The image jumps in as soon as it's loaded.

Sale badges and "only X left in stock" notices: these are often loaded afterwards via AJAX or JavaScript and appear after the first render — shifting content below them.

Cookie consent banners as layout pushes: many banner plugins insert an element at the top or bottom that pushes the rest of the page down — after the initial render.

Dynamic prices: if tiered prices, quantity discounts or personalised prices are calculated via JavaScript, the height of the price area changes after render.

Quick win

  • aspect-ratio for product images in CSS: ensures the browser reserves space before the image loads:
.woocommerce-product-gallery__image img,
.attachment-woocommerce_thumbnail {
    aspect-ratio: 1 / 1; /* adjust to your image format */
    width: 100%;
    height: auto;
}
  • Cookie banner as an overlay: set the banner plugin so it appears as a position: fixed overlay instead of a static element that shifts the layout.

Dev tip

Stabilise price areas with min-height so dynamic content (discounts, stock notices) doesn't cause a shift:

.woocommerce-variation-price,
.price {
    min-height: 2.5rem; /* adjust depending on the theme */
}

For AJAX-loaded badges (sale, sold out): include the container in the initial HTML already, but hide it via CSS, then only show it via JavaScript instead of inserting it fresh — that way there's no layout shift because the space is already reserved.

TTFB as the foundation

Before LCP, INP and CLS can even be improved, the server has to respond quickly. WooCommerce has an important quirk here: page caching is automatically disabled for logged-in users and for any session with a filled cart.

That means: as soon as a visitor adds a product to the cart, they get uncached PHP responses for every further page — including category pages and the homepage. Many caching plugins (WP Rocket, LiteSpeed Cache) have dedicated WooCommerce modes that handle this correctly. These modes have to be enabled explicitly.

Redis as an object cache is recommended as base infrastructure: even when the page cache doesn't apply, database queries get cached and PHP execution time is reduced.

You'll find a detailed guide to TTFB optimisation for WordPress in our TTFB article.

Monitoring WooCommerce performance continuously

A one-off PageSpeed measurement shows the current state — but not whether a WooCommerce update, a new plugin or a theme change has undone your performance work.

With turbometrics you can monitor your shop pages continuously: product pages, cart and checkout separately, with daily or hourly measurements. turbometrics measures LCP, INP, CLS and TTFB on every scan and shows you historical trends — you see immediately when a regression happened and can trace it back to a specific change.

Alert mode notifies you by email or webhook as soon as a CWV value drops below your defined threshold — before customers notice and before it shows up in your conversion rates.

Scan your WooCommerce shop for free now →

Try turbometrics for free

Scans, monitoring and live data — start for free, no subscription needed.

Get started for free