Where Product Page LCP Hides After You Fix Lazy Loading
Product page LCP survives every eager-loading fix. Four failure modes hide past the image, and each one can hold it above 8 seconds.

A merchant posted on the Shopify Community last November asking why the product page LCP on a customized OS 2.0 store still swung between 4.7 and 11 seconds after doing every optimization the guides recommend. Featured image preloaded with fetchpriority="high". First product image set to loading="eager". Srcset widths trimmed and the sizes attribute tuned for mobile viewports. All product photography converted to WebP. Compression applied. The homepage LCP was under 2 seconds. The product page kept fluctuating.
I bookmarked that thread because it sounded exactly like a store I had walked through the month before. Same fixes. Same shrug from PageSpeed Insights.
Fixing lazy loading is step one. Four other failure modes survive it, and any one of them can hold product page LCP above 8 seconds while your homepage passes.
Step one worked. The score didn't budge.
The obvious fix on a slow product page is the same one that works on a slow homepage. Find the first image in sections/main-product.liquid or sections/product-media.liquid, drop loading="lazy", add loading="eager" and fetchpriority="high". That is Shopify's own top recommendation on performance.shopify.com. Their internal data says 59% of Shopify stores lazy-load their LCP image on product pages against a broader-web median of 17%, and that one attribute alone costs about 3 seconds.
I applied that fix on the store I mentioned. PageSpeed Insights came back with the same three-digit mobile score to the tenth of a point. I ran the test three more times. Lab-environment noise on PSI is real - the same URL can wobble 2x between runs, and community moderators point that out any time somebody asks. My delta was zero, though, not noisy, and I had watched the eager attribute land in the source view.
The store's mobile LCP was 9.4 seconds before. It was 9.4 seconds after.
That is the moment when most merchants (and, honestly, the first twenty minutes of my afternoon) start looking at image compression. Every generic community thread on slow product pages ends up pointing at image weight. It rarely helps. On the store above, the JPEG was already under 90 KB and the CDN was serving WebP anyway.
What the LCP breakdown was actually saying
The turn came from a phase I never looked at closely.
PSI's mobile Lighthouse report has a section called "LCP element" and, one panel further down, a subsection that splits total LCP time across four phases: TTFB, Load Delay, Load Duration, and Render Delay. On the homepages where the standard guides work, the whole story lives inside Load Duration. On product pages, that split is where the story of slow product page LCP actually lives.
The store I was working on had TTFB around 700ms, Load Delay around 400ms, Load Duration around 1.2 seconds. Those three phases were fine. Render Delay was 6.9 seconds.
Render Delay is the gap between "the browser has the image bytes" and "the browser paints the pixel." A number above 500ms there means the image is not what is holding you up. Something is holding the main thread after the download finished. A merchant in April 2024 posted a similar breakdown, showing roughly a 10-second load delay and a separate 1.65-second render delay after doing every image-side fix, and the thread stopped without a resolution because community advice kept pointing back at the image.
Once I switched from "why is the image slow" to "what is holding the paint," three specific failure modes stopped being invisible.
The preload that was pulling a different file
The first hole was in a place I would not have looked without a Network tab open.
Somebody had added a <link rel="preload" as="image"> in theme.liquid months earlier, pointing at the featured product image URL. It looked correct in the source. Lighthouse even scored it as a hit: "LCP element had an explicit preload."
The preload URL and the img srcset were pointing at different files.
The preload URL had .jpg and a width=1200 parameter. Shopify's CDN was serving the gallery .webp at width=800, with a crop=center parameter that produced a 3:4 portrait from a landscape source. Two separate CDN cache entries. The browser fetched the preloaded one first, discarded it when the srcset picked the different URL, then fetched the actual image again. Two downloads, one wasted, and the "correct" one starting later than it would have with no preload at all.
A community developer named tim_tairli has been documenting exactly this pattern for over a year on threads with essentially no other answers. The phrase "self-generated preload elements load different images than actual img tags" comes from his replies. Once you know to look for it, you see it everywhere.
The fix uses Shopify's image_tag Liquid filter, which has a preload: true parameter that generates a server-side preload header always matching the image the tag renders. Same URL, same width, same crop, same format. Drop the manual <link> in theme.liquid, switch to the image_tag preload flag on the first gallery image, and the double-fetch is gone.
The image that was done five seconds before it appeared
The second hole is documented on performance.shopify.com and almost never surfaced in community threads. Michael Gooding on Shopify's performance engineering team wrote it up in March 2023 with a Case-Mate case study, and I keep the link bookmarked because I open it more than any other Shopify performance post.
Some themes wrap the first product image in an element with opacity: 0 in the default CSS and a JavaScript-driven fade-in transition to opacity: 1. Browsers do not record LCP until the element becomes visible. The image file can be fully downloaded at second 5 and LCP does not get recorded until the fade-in completes at second 13.
That is the entire gap. Roughly eight seconds where nothing is downloading, nothing is failing, and the image already exists on the device. The browser is just waiting for the CSS to un-hide it.
Case-Mate's fix was to remove a reveal attribute from the image, and their measured LCP dropped from 13 seconds to about 7. Six seconds of pure recording delay recovered with no visual change to the shopper. Loading the transition CSS asynchronously in a deferred stylesheet was the alternative - a compromise that kept the fade-in but shrunk the delay to roughly half a second.
Search your theme's CSS for opacity: 0 combined with transition on anything wrapping the main product image. On the store I was working on, it lived on the parent .product-media__container div, not the img element itself, which is exactly the shape the Case-Mate write-up flags.
If you want a second pair of eyes on your product page LCP breakdown before you start editing theme CSS, the first 15 minutes with a help1 expert is free and the LCP phases panel is where we always start.
What I check first on these stores now
Some of what I check will not apply to your store. I have not tested this whole flow on Shopify Plus checkout-extensibility stores or on Hydrogen headless setups, and I would not assume the failure modes transfer cleanly there. On standard Online Store 2.0 themes, though, I run the same five checks in the same order every time.
- Is the URL being tested on the actual published live theme? Development themes routinely show 8-10 second LCP because Shopify's CDN does not fully cache preview URLs. That was confirmed on the Shopify Developer Community last summer. If a merchant DMs me a preview URL, my first message back is always "please test the live theme."
- Is the LCP element the first product image? If Lighthouse names an announcement bar, a chat widget button, or a popup, that overlay is your LCP and no amount of image work helps.
- Does the phases breakdown show a render delay above 500ms? If yes, the bottleneck lives in JavaScript or CSS, not the image. Gallery libraries and product option apps are the usual culprits.
- If a manual preload tag exists, does its URL match what the img srcset resolves to at the mobile breakpoint? Open DevTools, Network, filter Img. Find the first image, check the Initiator column. If it says "preload" and the URL matches, the preload is doing work. Anything else and it is wasted.
- Search the theme CSS for
opacity: 0andtransitionon containers wrapping the LCP image.
These five checks take about ten minutes with a browser and no developer access. What they leave out is what most product page LCP threads assume you need first: heavier image compression, WebP conversion (Shopify's CDN already handles that), and tighter srcset widths.
One more thing that is worth naming. The Prestige theme, one of the most popular paid themes on the Shopify Theme Store, treats the first product section as a slideshow carousel by default. That carousel initializes JavaScript before the image can paint, and standard eager loading does not bypass it. If you are on Prestige and every image-side fix has failed to move the number, that carousel is worth testing with a mobile-only static image replacement. A May 2025 community thread walked through the exact pattern with a developer named Emre_SecretHero, and it is where I look on any Prestige store now.
There is a smaller sibling to all of this on the homepage side, which our earlier post on Shopify page speed hero image LCP covers - the fix that does work on homepages, and why it does not transfer to product pages.
Product page LCP keeps looking like an image problem right up to the moment you notice the image was ready five seconds before it appeared.
Still stuck? Talk to an expert.
Our vetted Shopify experts can fix this issue for you in a live session. $39 per session. Your first 15 minutes are free.