All articles
E-commerceAugust 31, 2026 6 min read

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.

Product Page LCP on Shopify: Hitting Sub-2s Without Ripping Out the Theme

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-vitals shipper

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:

  1. fetchpriority="high" on the LCP image. Browsers otherwise deprioritize images behind CSS and fonts.
  2. width and height attributes — mandatory, or you'll pay for it in CLS.
  3. 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 defer or after DOMContentLoaded.
  • 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:

  1. Add RUM and segment by template. Get a real p75 LCP number for PDPs specifically.
  2. Fix the hero image: correct srcset, fetchpriority="high", no lazy load, preload if needed.
  3. Move every third-party script into buckets B or C. Delete anything without an owner.
  4. Preload one font weight, drop the rest.
  5. 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.

#Shopify#Performance#Core Web Vitals#CRO#E-commerce

Want a team like ours?

72Technologies builds production software for the kind of teams who actually read this blog.

Start a project