Product Page LCP on Shopify: Hitting Sub-2s Without Ripping Out the Theme
A practical breakdown of what actually moves LCP on Shopify product pages — image pipelines, third-party scripts, Liquid patterns — without going headless or rebuilding the theme from scratch.

Most Shopify stores we audit have a product page LCP somewhere between 3.4 and 5.2 seconds on mobile. The theme isn't the villain, and going headless isn't the fix. The villain is almost always the same short list of things — and you can usually get under 2 seconds without touching the checkout, the CMS, or the merchandiser's workflow.
This is what we actually do, in the order we do it, when a client hands us a slow product page and asks us to make it fast.
Why LCP is the number that matters on PDPs
On a product detail page, the Largest Contentful Paint element is almost always the hero product image. That's convenient, because it means LCP is a proxy for "how long until the shopper sees what they came to see." Google uses it as a Core Web Vitals signal, but more importantly, it correlates tightly with bounce rate on mobile traffic from paid channels.
In our experience, moving PDP LCP from ~4s to ~1.8s on mobile tends to lift add-to-cart rate somewhere in the 6–14% range, depending on how much of the traffic is paid and how impatient the audience is. Don't quote that as gospel — run your own A/B — but it's a real effect, not a rounding error.
The target we aim for:
- Mobile LCP: under 2.0s at the 75th percentile
- CLS: under 0.05
- INP: under 200ms
Getting there on Shopify is mostly a discipline problem, not an architecture problem.
Step one: measure the right thing
Before you touch anything, get field data. Lighthouse scores are lab data — they lie to you in both directions. Use:
- Chrome UX Report (CrUX) via PageSpeed Insights for the 28-day p75
- Shopify Web Performance dashboard in the admin (it's rough but useful for trend)
- A real RUM tool if you have one — SpeedCurve, DebugBear, or a self-hosted
web-vitalsshipper
For RUM on a budget, this snippet in theme.liquid will get you started:
<script type="module">
import {onLCP, onCLS, onINP} from 'https://unpkg.com/web-vitals@4?module';
const send = (metric) => {
navigator.sendBeacon('/apps/vitals', JSON.stringify({
name: metric.name,
value: metric.value,
id: metric.id,
path: location.pathname,
template: {{ template | json }}
}));
};
onLCP(send); onCLS(send); onINP(send);
</script>
Route /apps/vitals to whatever collector you like. The point is: segment by template. PDP performance is a different problem from collection or cart performance, and averaging them together hides everything.
Step two: fix the image pipeline
This alone is usually worth 1–2 seconds of LCP on mobile.
Serve the right size, not the biggest one
Shopify's image CDN is powerful and most themes underuse it. The Dawn-style pattern is fine, but audit it. You want the srcset to actually match your rendered sizes:
{% assign img = product.featured_image %}
<img
src="{{ img | image_url: width: 800 }}"
srcset="
{{ img | image_url: width: 400 }} 400w,
{{ img | image_url: width: 600 }} 600w,
{{ img | image_url: width: 800 }} 800w,
{{ img | image_url: width: 1200 }} 1200w
"
sizes="(min-width: 750px) 50vw, 100vw"
width="{{ img.width }}"
height="{{ img.height }}"
fetchpriority="high"
loading="eager"
decoding="async"
alt="{{ img.alt | escape }}"
/>
Three things people forget:
fetchpriority="high"on the LCP image. Browsers otherwise deprioritize images behind CSS and fonts.widthandheightattributes — mandatory, or you'll pay for it in CLS.- Never
loading="lazy"on the LCP image. We see this weekly. A lazy-loaded hero image can add 800ms to LCP because the browser waits for layout before requesting it.
Preload when the image is behind JS
If your gallery is powered by a JS library that swaps the src, add a preload in the <head>:
<link rel="preload" as="image"
href="{{ product.featured_image | image_url: width: 800 }}"
imagesrcset="..."
imagesizes="(min-width: 750px) 50vw, 100vw"
fetchpriority="high">
This moves the request from "after JS parses" to "as soon as HTML arrives." It's often the single biggest win.
Step three: audit apps like they owe you money
Open the Network tab on a real device. Sort by size. Every request that isn't Shopify's or yours needs to justify itself.
Common offenders we've killed or deferred on client stores:
- Review widgets loading a 400KB bundle on page load to render zero reviews above the fold
- Upsell apps injecting three script tags before the hero renders
- Analytics stacks: GA4 + Meta Pixel + TikTok Pixel + a CDP + Hotjar, all synchronous
- Currency converters that block render while they fetch exchange rates
The 3-bucket rule
For every third-party script, put it in one of three buckets:
- Bucket A — critical for LCP: almost nothing belongs here. Consent Mode maybe.
- Bucket B — needed for the page but not for first paint: load with
deferor afterDOMContentLoaded. - Bucket C — needed later or on interaction: load on scroll, on idle, or on the first user gesture.
Most review widgets, upsell widgets, chat, and analytics belong in C. If the app doesn't let you defer it, that's a real reason to replace it. Shopify's own app performance report in the admin will show you the impact per app.
Step four: unclog the critical path
After images and apps, the remaining LCP damage is usually in the theme itself.
Fonts
Self-host through the Shopify assets pipeline or use font-display: swap. Preload the one font weight your hero uses. Don't preload six.
<link rel="preload" href="{{ 'inter-600.woff2' | asset_url }}"
as="font" type="font/woff2" crossorigin>
CSS
Dawn and modern themes are reasonably lean, but section-heavy themes often ship a 200KB stylesheet where 30KB is used above the fold. If you can't split it cleanly, at least make sure it's not blocked behind an app's stylesheet.
Liquid loops
Watch for N+1 patterns in related products or recommendations. A {% for %} over collections.all.products with tag filtering inside will absolutely tank TTFB on stores with real catalogue depth. Use the Search & Discovery API or product_recommendations instead.
Step five: the boring wins nobody talks about
- Turn on Shopify's built-in image lazy loading everywhere except the hero.
- Remove unused theme sections. Dead code still ships to the browser.
- Prune metafields you render but don't use — every one is a Liquid render cost.
- Check your Shopify plan. Shopify Plus gets meaningful edge and checkout improvements. It's not always worth it, but for stores over ~$2M/year it usually is.
- Kill the pre-loader animation. Yes, really. That "nice" fade-in overlay is delaying LCP by whatever its duration is.
When headless is actually the answer
We'll say it plainly: for most Shopify stores under $10M/year, a well-tuned Liquid theme will beat a rushed Hydrogen or Next.js rebuild on real-world performance, and cost a tenth as much to maintain. Headless earns its keep when you have:
- A content model that genuinely doesn't fit Shopify's admin
- Multiple storefronts sharing one commerce backend
- A design system already used across other properties
- Engineering capacity to own a build pipeline forever
If none of those apply, fix the theme first. We've written more on the headless tradeoffs on our services page if you want the longer version.
Where we'd start on Monday
If you inherited a slow Shopify PDP tomorrow, the first-week plan is boring and effective:
- Add RUM and segment by template. Get a real p75 LCP number for PDPs specifically.
- Fix the hero image: correct
srcset,fetchpriority="high", no lazy load, preload if needed. - Move every third-party script into buckets B or C. Delete anything without an owner.
- Preload one font weight, drop the rest.
- Re-measure after 7 days of field data, not lab data.
That's usually enough to get from 4s to under 2s. The rest is polish — and polish only matters once the foundations aren't leaking seconds.
Want a team like ours?
72Technologies builds production software for the kind of teams who actually read this blog.
Start a projectKeep reading

Inventory Sync Between Shopify and Your ERP: Why It Breaks and How to Design It Right
Every mid-market Shopify store eventually oversells or ghost-stocks its way into a support crisis. Here's how to design an inventory sync that survives Black Friday, backorders, and a flaky ERP.
Product Page LCP on Shopify: Getting Under 2.0s Without Ripping Out Your Theme
A tactical breakdown of how to cut Largest Contentful Paint on Shopify product pages to under 2.0s — without going headless, rebuilding your theme, or fighting every app you have installed.
WhatsApp Checkout in LATAM and MENA: What Actually Converts
Conversational commerce isn't a novelty in Brazil, Mexico, Egypt or the UAE — it's the default. Here's what we've learned wiring WhatsApp into real Shopify and headless stacks, and where the funnel quietly breaks.
