All articles
E-commerceSeptember 24, 2026 6 min read

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.

Most Shopify stores we audit have the same problem on their product pages: a hero image that decodes late, a theme that ships 400KB of JavaScript before the buy button paints, and a review widget that blocks the main thread for a full second. The good news — you rarely need to go headless to fix it. The bad news — the fixes are boring, and nobody on your team wants to own them.

This is the playbook we use when a client asks us to get their PDP Largest Contentful Paint under 2.0 seconds on 4G mobile without a full replatform.

Why the PDP is the page that matters

Homepage LCP gets all the attention because it's what Lighthouse audits by default. But on a healthy Shopify store, 60–80% of paid traffic lands directly on a product page. That's where the money is, and that's where Google's Core Web Vitals actually influence rankings for commercial queries.

A PDP has a harder job than a homepage:

  • It has to render a large hero image (usually the LCP element)
  • It's personalized (currency, inventory, sometimes price)
  • It carries the most third-party weight: reviews, upsells, size guides, trust badges
  • It's the page merchandisers touch most, so it accumulates cruft fastest

Our working target is LCP under 2.0s at the 75th percentile on mobile, INP under 200ms, and CLS under 0.05. Google's "good" threshold for LCP is 2.5s — we aim lower because e-commerce conversion falls off a cliff well before you're technically "failing."

Step 1: Find the actual LCP element (it's probably not what you think)

Before you touch anything, open Chrome DevTools, go to the Performance panel, throttle to Slow 4G and 4x CPU, and record a PDP load. Look for the LCP marker in the timeline.

On Shopify PDPs, the LCP element is one of three things:

  1. The main product image (most common)
  2. The product title <h1> (if the image is lazy-loaded incorrectly)
  3. A hero banner section above the product gallery (on themes like Dawn variants)

If your LCP is the <h1>, that's usually a sign your main product image has loading="lazy" when it shouldn't. Fix that first — it's a one-line change with a two-second payoff.

The image is almost always the bottleneck

Shopify's CDN is fast, but the default Liquid image helpers ship more bytes than you need. A typical product.featured_image at full width on mobile is 1200px wide, served as JPEG, around 180KB. You want it under 60KB, in AVIF or WebP, at the actual rendered width.

{%- 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="(max-width: 749px) 100vw, 50vw"
  width="{{ img.width }}"
  height="{{ img.height }}"
  alt="{{ img.alt | escape }}"
  fetchpriority="high"
  decoding="async"
>

Three things worth calling out:

  • fetchpriority="high" on the LCP image is the single highest-leverage attribute you can add. In our experience it shaves 200–500ms off LCP on cold mobile loads.
  • width and height attributes prevent CLS. Don't skip them.
  • Never put loading="lazy" on the LCP image. Ever. This is the number one mistake we see in custom themes.

Step 2: Preload the hero image and preconnect to Shopify's CDN

Add this to your theme.liquid <head>, ideally inside a conditional that only fires on product templates:

{%- if template contains 'product' -%}
  <link rel="preconnect" href="https://cdn.shopify.com" crossorigin>
  {%- assign hero = product.featured_image -%}
  <link
    rel="preload"
    as="image"
    href="{{ hero | image_url: width: 800 }}"
    imagesrcset="
      {{ hero | image_url: width: 400 }} 400w,
      {{ hero | image_url: width: 800 }} 800w,
      {{ hero | image_url: width: 1200 }} 1200w"
    imagesizes="(max-width: 749px) 100vw, 50vw"
    fetchpriority="high">
{%- endif -%}

The preload tells the browser to start fetching the hero image before it's finished parsing the CSS. On a 400ms mobile round-trip, that matters.

Step 3: Audit your apps like a landlord evicting bad tenants

Open the Network panel, filter by JS, and sort by size. You'll usually find:

  • A reviews app injecting 150KB+ of script before the fold
  • An upsell/cross-sell app loading its own React bundle
  • A currency converter that fires on every page
  • Two analytics tags doing the same job
  • A "free gift" app that hasn't been used since 2023

For each script, ask: does this need to run before LCP, or ever, on this page?

Most review widgets don't. The star rating in the buy box can be rendered server-side from a metafield you sync nightly — the actual review carousel can lazy-load on scroll or on click. Talk to the app vendor; most of them now support a "lazy mode" or an API-only integration. If they don't, replace them.

The defer and async trap

Adding defer to a <script> tag doesn't make it free. It still parses, compiles, and executes — it just doesn't block HTML parsing. On mid-range Android devices, parsing 300KB of deferred JavaScript can still block the main thread for 400ms and destroy your INP score.

The real question isn't when the script runs, it's whether it needs to exist on this page at all.

Step 4: Kill render-blocking CSS you're not using

Most Shopify themes ship a single stylesheet that covers every template — cart, collection, product, blog, checkout accessories. On the PDP, you're probably using 20% of it.

You have two realistic options:

  1. Inline critical CSS for the above-the-fold PDP layout and load the rest with media="print" onload="this.media='all'". Ugly but effective.
  2. Split your stylesheet per template using Liquid, so product.liquid only pulls in product.css.

Option 2 is cleaner but harder to maintain. Option 1 is what we usually ship first, then refactor once we've bought the LCP headroom.

Step 5: Measure real users, not just your MacBook

Lighthouse on your laptop lies. It runs on a fast CPU with a synthetic network profile, and your PDP will always look better in the lab than in the field.

Use Chrome's Web Vitals JavaScript library to send real LCP, INP, and CLS to your analytics. On Shopify, this is a five-minute install:

<script type="module">
  import {onLCP, onINP, onCLS} 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
    }));
  };
  onLCP(send); onINP(send); onCLS(send);
</script>

Pipe it into whatever you already use — GA4, a custom endpoint, Datadog. What you want is the 75th percentile LCP for /products/* broken down by device type. That number is what Google actually uses.

Step 6: Know when the theme is the ceiling

Sometimes you'll do all of the above and still land at 2.4s LCP. At that point you're fighting the theme itself: its section rendering model, its global JavaScript bundle, or a vendor lock-in that makes real optimization impossible.

That's when it's worth asking whether Hydrogen, a custom Remix storefront, or a lighter theme like Shopify's Dawn (as a base) is the right move. But do that math honestly — going headless to save 400ms of LCP when you have a 6-second checkout is bad prioritization. Look at the whole funnel first.

If you want a second opinion on where your storefront is bleeding, our e-commerce engineering team does this kind of audit regularly.

Where we'd start

If you have one afternoon: add fetchpriority="high" to your PDP hero image, remove loading="lazy" from anything above the fold, and preload the hero. That alone typically buys 300–600ms.

If you have a week: install real user monitoring, audit every app that injects a script on the PDP, and negotiate lazy-loading with the vendors that matter. Delete the ones that don't.

If you have a month: split your CSS per template, move review data into metafields so it renders server-side, and rebuild the parts of your theme that assume a desktop user on Wi-Fi. Your conversion rate will thank you before your Lighthouse score does.

#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