WooCommerce LCP: shrink the gallery first
Three product pages, three different LCP elements: the gallery on the PDP, the hero block on the homepage, a category banner on /shop/. That’s WooCommerce in a nutshell, and it’s why we tackle this at the server layer with ModPageSpeed (an nginx or Apache module) instead of pasting loading="eager" into 40 theme files. Shrink the gallery image at the source, kill the cart-fragments AJAX call where it doesn’t belong, and let the rewriter apply the fix to the whole catalog at once.
What LCP measures
LCP — Largest Contentful Paint — is the render time of the largest above-the-fold element on a page. On a WooCommerce product page that is almost always the main gallery image; on category pages it’s the first product card; on the homepage it depends on the theme. Google considers anything under 2.5 s “good”; anything over 4 s is “poor”. The field measurement comes from Chrome’s CrUX dataset (real users over the last 28 days); the lab measurement comes from Lighthouse and PageSpeed Insights. The two often disagree because real users are slower than your developer machine. See web.dev/articles/lcp for the canonical definition.
The most common LCP failures on WooCommerce
Almost every failing WooCommerce PDP fails for the same reason — gallery image weight. The other two failure modes are below:
-
Product galleries lazy-load eagerly, but in the wrong order. Storefront and WooCommerce Blocks insert the product gallery with
data-large_imageURLs at full upload resolution — often 2000–3000 px wide. On a product page, the LCP element is usually the main gallery image, but the lazy-loader (woocommerce.flexslider.js) defers it until JS executes, which means LCP waits on the cart-fragments AJAX call. Net effect: LCP firing at 3.5–5 s on 4G even with a moderately sized image. -
wc-cart-fragments.jsruns on every page. This AJAX call refreshes the mini-cart count viaadmin-ajax.php, fires on every page including the homepage and blog, and blocksdomInteractive. Pages with the cart-fragments call typically show a 600–900 ms gap between FCP and LCP on slow connections. -
Product images aren’t regenerated after a theme switch. A common deploy: the theme changes from Storefront to a “premium” theme, the new theme declares a different
add_image_size( 'shop_single', 800, 800 ), butwp-content/uploads/still holds the old sizes. WordPress serves the closest available size, which on most stores is the 2048-wide original. LCP regresses and nobody notices because the image still “looks fine”.
Find the gallery image that’s blowing the budget
Before changing anything, get the baseline and the attribution.
- Run PageSpeed Insights on a representative product URL:
https://pagespeed.web.dev/analysis?url=<URL>. Read the Largest Contentful Paint element box. The element selector usually points at the main gallery<img>; if it points at the<h1>instead, your gallery is being lazy-loaded out of LCP candidacy entirely (which is its own problem — see failure mode #1). - Open Chrome DevTools → Network panel → filter on
Img, sort by Size. Anything over 200 KB above the fold is a candidate for the server-side rewrite. Anything over 500 KB is the LCP culprit on most stores. - Open DevTools → Performance panel → record under mobile throttling (Slow 4G + 4× CPU). Click the LCP marker on the Timings track and read the Related Node.
- For repeatable measurement:
npx lighthouse <PRODUCT_URL> --only-categories=performance --form-factor=mobile. - In WP Admin → WooCommerce → Status, scroll to Tools and look at the registered image sizes. If
shop_singleis 800×800 but the underlying upload is 2048×2048, you have the regenerate-thumbnails problem.
Output you’re hunting for: the LCP element’s selector, its file size, and its rendered dimensions. Disagreement between file dimensions and rendered dimensions (a 2000-px file served into an 800-px slot) is the most common LCP cause.
Step 2: Resize and recompress catalog images with resize_images + convert_jpeg_to_webp
ModPageSpeed runs as an nginx module (most WooCommerce stores are nginx-fronted) or an Apache module. It rewrites HTML on the way out and serves optimized image variants from a shared cache. The filters that move LCP on WooCommerce:
recompress_images— re-encodes JPEGs and PNGs at a configurable quality threshold. On photographic product imagery, dropping from quality 90 to 78 typically saves 30–50% of bytes with no visible difference.convert_jpeg_to_webp— transcodes to WebP for browsers that advertise support. The variant is served from cache on subsequent requests at HIT speed.resize_images— inspects the rendered<img width=...>and recompresses to that pixel target, regardless of upload size, so a 2000-px upload served into an 800-px slot gets resized server-side. This is the real win on WooCommerce, where image-size mismatches are the rule rather than the exception.inline_preview_images— ships a sub-1 KB low-quality placeholder inline in the HTML for above-the-fold images, so the first paint shows the LQIP while the gallery JS is still parsing.
For photographic catalogs, set pagespeed ImageRecompressionQuality 78. The default is 85, but 78 is the threshold where SSIM stays above 0.95 on typical e-commerce photography. Verify on your own catalog with a side-by-side comparison before rolling out.
Why a rewriter beats a per-image audit on WooCommerce: the theme + plugin ecosystem makes per-image hygiene impractical at scale. Regenerating thumbnails after every image-size change works, but it’s a manual step that drifts. A rewriter applies the size + format + compression policy automatically to every page in the catalog, including the products the merchandising team uploads next week.
Minimal nginx config:
pagespeed on;
pagespeed FileCachePath /var/cache/ngx_pagespeed;
pagespeed RewriteLevel CoreFilters;
pagespeed EnableFilters recompress_images;
pagespeed EnableFilters convert_jpeg_to_webp;
pagespeed EnableFilters resize_images;
pagespeed EnableFilters inline_preview_images;
pagespeed ImageRecompressionQuality 78;
After enabling, re-run PSI. LCP attribution should show the rewritten WebP variant being served instead of the original JPEG, and the element render delay should drop.
What WooCommerce can fix on its own
What you can do inside WooCommerce to stack additional wins:
- Disable
wc-cart-fragments.json non-shop pages. The snippet circulates widely; infunctions.php, dequeuewc-cart-fragmentsoutsideis_woocommerce(),is_cart(), andis_checkout(). Roughly six lines. - Run the “Regenerate Thumbnails” plugin after any image-size change. This is the WooCommerce-side fix for failure mode #3.
- Set
add_filter( 'jpeg_quality', fn() => 82 )infunctions.phpto reduce upload-time JPEG size. Pairs cleanly with the server-side recompression; the smaller upload means the rewriter has less to do. - For the homepage or a featured-product showcase, hand-pick the LCP image URL and add a
<link rel="preload" as="image" fetchpriority="high">inheader.php. This is the one place an explicit preload outperforms anything the rewriter can infer. - Disable WooCommerce Blocks on stores not using the new block-based checkout. The Blocks bundle adds roughly 80 KB of JS that most non-shop pages don’t need.
How to confirm the gallery shrank
- DevTools → Network panel → reload the product URL, filter on
Img, confirm the gallery image is served withcontent-type: image/webp(assuming the client supports WebP) and the transferred size matches the rendered slot. - Open the page with
?PageSpeedFilters=+debug— the response includes HTML comments naming which filters ran on which assets. - Re-run PSI on the same product URL. LCP attribution should show the optimized variant (
?PageSpeed=variant&v=...or a hashed filename, depending on configuration). Element render delay should drop. - After 28 days, check Search Console → Core Web Vitals report. The
/product/URL group should move from “Needs improvement” to “Good”. Watch field data, not just lab.
A drop-in nginx snippet
# nginx — minimal config to address LCP on WooCommerce
pagespeed on;
pagespeed FileCachePath /var/cache/ngx_pagespeed;
pagespeed RewriteLevel CoreFilters;
pagespeed EnableFilters recompress_images;
pagespeed EnableFilters convert_jpeg_to_webp;
pagespeed EnableFilters resize_images;
pagespeed EnableFilters inline_preview_images;
pagespeed EnableFilters hint_preload_subresources;
# Tune recompression quality for photographic catalogs
pagespeed ImageRecompressionQuality 78;
For Apache:
ModPagespeed on
ModPagespeedFileCachePath /var/cache/mod_pagespeed
ModPagespeedRewriteLevel CoreFilters
ModPagespeedEnableFilters recompress_images
ModPagespeedEnableFilters convert_jpeg_to_webp
ModPagespeedEnableFilters resize_images
ModPagespeedEnableFilters inline_preview_images
ModPagespeedImageRecompressionQuality 78
The full directive reference lives in the filter reference.
When this doesn’t work
Cases where ModPageSpeed alone isn’t enough on WooCommerce:
- The gallery is rendered by a JavaScript SPA (WC Blocks Checkout, headless WP front-end). ModPageSpeed rewrites server-rendered HTML; it cannot rewrite a React tree that constructs the gallery in the browser. For headless setups, image optimization moves to the build step (Next.js
<Image>) or to a CDN-side image-resizing service. - TTFB is the long pole. WooCommerce’s uncached database queries on the product page (variations, related products, reviews) can take 800–1500 ms on under-provisioned hosting. No HTML rewriter can shorten a 1 s TTFB; the LCP minimum becomes
TTFB + image-render-time. Fix the cache stack first (object cache + Varnish or LiteSpeed Cache) before the rewriter. - The LCP is the checkout button on
/cart/. Cart and checkout are interactive flows where the LCP isn’t really the user’s problem — interactivity (INP) is. See How to fix INP on WooCommerce for that path.
Related
- How to fix INP on WooCommerce
- How to fix CLS on WooCommerce
- How to fix LCP on WordPress
- The economics of image optimization
- ModPageSpeed filter reference
ModPageSpeed runs as an nginx or Apache module; a 14-day trial starts from /pagespeed_global_admin (card-at-start via FastSpring). See license terms.
Read next
-
WooCommerce CLS: lock the gallery
How to fix CLS on WooCommerce: stop the gallery shift, lock variation swap dimensions, reserve cross-sell space, and rewrite img tags at the server.
-
INP on WooCommerce: where the cost lives
How to fix INP on WooCommerce: server-layer JS minification reduces the parse cost, but cart and variation handlers are architectural. The plan inside.
-
ASP.NET Core LCP: Razor vs Blazor
How to fix LCP on ASP.NET Core: async view components, cached static files, and the WeAmp.PageSpeed middleware. Practical steps for Razor Pages and MVC in 2026.