All articles
E-commerceOctober 24, 2026 6 min read

Shopify Product Page LCP Under 2 Seconds: A Core Web Vitals Playbook

Most Shopify product pages ship with an LCP between 3 and 5 seconds. Here's the engineering checklist we run through to drag that number under 2, without ripping out the theme or going headless.

Most Shopify stores we audit ship product pages with a Largest Contentful Paint somewhere between 3 and 5 seconds on a mid-tier Android over 4G. That's not a theme problem, and it's rarely a Shopify problem — it's a stack-of-decisions problem. This is the checklist we work through when a merchant asks us to get LCP under 2 seconds without going headless.

Why 2 seconds, and why LCP specifically

Google's "good" threshold for LCP is 2.5 seconds at the 75th percentile. We aim for 2.0 because the field data always lags the lab data by 15–30%, and because the real competitors on a product detail page aren't other Shopify stores — they're Amazon and whatever marketplace dominates your region. If your hero image paints in 1.8 seconds and theirs paints in 1.2, you lose the scroll.

LCP is also the one Core Web Vital that correlates most directly with revenue in our experience. CLS matters, INP matters, but a slow hero image is what makes a shopper bounce before they've even seen the price.

What actually counts as the LCP element on a PDP

On almost every Shopify product page we've measured, the LCP element is one of three things:

  • The main product image (most common)
  • The product title h1 (if the image lazy-loads incorrectly)
  • A hero banner above the product gallery (common on themes like Impulse or Prestige)

Before you optimize anything, open Chrome DevTools → Performance Insights → LCP, and confirm which element it is. We've watched teams spend a week optimizing images when the real LCP was a h1 blocked by a web font.

Step 1: Fix the hero image before anything else

Shopify's image_url filter and image_tag do most of the work for you, but defaults are wrong for a hero. Here's what we ship:

{%- assign hero = product.featured_media.preview_image -%}
<img
  src="{{ hero | image_url: width: 800 }}"
  srcset="
    {{ hero | image_url: width: 400 }} 400w,
    {{ hero | image_url: width: 600 }} 600w,
    {{ hero | image_url: width: 800 }} 800w,
    {{ hero | image_url: width: 1200 }} 1200w
  "
  sizes="(min-width: 750px) 50vw, 100vw"
  width="{{ hero.width }}"
  height="{{ hero.height }}"
  alt="{{ hero.alt | escape }}"
  fetchpriority="high"
  loading="eager"
  decoding="async">

Four things matter here:

  1. fetchpriority="high" — tells the browser to pull this image before scripts, fonts, or lower-priority resources.
  2. loading="eager" — never lazy-load the LCP image. We see this mistake constantly because theme developers apply loading="lazy" globally.
  3. Explicit width and height — prevents CLS and lets the browser reserve space.
  4. Tight sizes attribute — most themes ship sizes="100vw", which forces the browser to download a 1200w image on desktop even though the slot is 600px wide.

Shopify's CDN serves WebP automatically when you use image_url, so you don't need to convert anything manually. Just stop uploading 4000px source images — resize to 2000px max before upload, because the CDN still has to transform the source and that eats TTFB on first request.

Step 2: Strangle the theme JavaScript

Dawn is reasonably fast. Most paid themes are not. The usual suspects:

  • A full jQuery bundle loaded in the head
  • Slick, Slideshow, or Flickity for the product gallery
  • A "product recommendations" section that fetches above-the-fold
  • Currency conversion scripts that block render

The one-line audit

In Chrome DevTools, open Coverage (Cmd+Shift+P → "Coverage"), reload the PDP, and sort by unused bytes. If any single JS file has more than 60% unused code and loads in the head, it's a candidate for defer or removal.

For the gallery specifically, modern CSS can do what Flickity used to do:

.product-gallery {
  display: flex;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  gap: 1rem;
}
.product-gallery > img {
  scroll-snap-align: start;
  flex: 0 0 100%;
}

We've shipped this on three stores in the last year and nobody noticed the "carousel" was gone — they just noticed the page felt faster.

Step 3: Audit your apps like they owe you money

This is where most of the LCP damage lives. Every Shopify app that injects a script tag via ScriptTag API or theme app extension adds between 20 and 300ms of blocking work. Open the Network tab, filter by JS, and sort by size. You'll typically find:

  • A review app (Judge.me, Yotpo, Loox) loading 150–400KB above the fold
  • An upsell app (ReConvert, Bold) with its own React bundle
  • A "recently viewed" app that could be 20 lines of localStorage
  • Two analytics apps doing the same thing

The ruthless rule

If an app injects more than 50KB of JavaScript and you can't point to revenue it generated in the last 90 days, uninstall it. Then — and this is the part teams forget — check the theme code for leftover script tags, because uninstalling an app does not always remove its snippet.

For review widgets specifically, almost all of them support lazy initialization. Yotpo has yotpo.initWidgets() you can call on scroll. Judge.me has a lazy-loading data attribute. Use them. The star rating in the product title can be rendered server-side from the app's API during a nightly sync into a metafield — no runtime script needed for the above-the-fold stars.

Step 4: Kill render-blocking fonts

Shopify themes love Google Fonts via @import, which is the slowest possible way to load a font. Switch to <link rel="preload"> with font-display: swap, or self-host via the assets folder:

<link rel="preload"
      href="{{ 'Inter-Regular.woff2' | asset_url }}"
      as="font"
      type="font/woff2"
      crossorigin>

Self-hosted fonts on Shopify's CDN are consistently 100–300ms faster than fonts.googleapis.com in our measurements, because you cut out a DNS lookup and a redirect.

Step 5: Handle third-party tags without destroying LCP

Facebook Pixel, TikTok Pixel, Klaviyo, GTM — these all want to load as early as possible for attribution accuracy. They also all tank LCP if you let them run on the main thread during initial paint.

The compromise we use:

  • Load GTM with async in the head (not defer, GTM needs async)
  • Fire pixels on requestIdleCallback with a 2-second fallback timeout
  • Use Shopify's Customer Events (web pixels) sandbox for anything that supports it — these run in a worker and don't touch your main thread

Shopify's web pixel sandbox is underused. If your pixel vendor supports it (most do now), moving them out of the theme into the sandbox typically shaves 200–500ms off Total Blocking Time.

Step 6: Measure the right way

Lighthouse in DevTools lies. It runs on your laptop, over your fibre connection, with no extensions loaded. Use these instead:

  • PageSpeed Insights for field data (CrUX) — this is what Google actually ranks on
  • WebPageTest with a Moto G Power profile on 4G for lab testing
  • Shopify's Web Performance dashboard in the admin — it's CrUX data filtered to your store

We test every change against WebPageTest's Moto G Power + 4G profile because that's roughly the median device for most of our clients' traffic. If LCP is under 2 seconds there, it's under 2.5 seconds for 75% of real users.

Where we'd start

If you've got one afternoon, do this in order: confirm the LCP element in DevTools, fix the hero image attributes (fetchpriority, eager, correct sizes), and uninstall every app you can't justify. That alone has moved stores from a 4.2s LCP to a 2.1s LCP in a single sprint. Everything else — fonts, pixels, gallery rewrites — is cleanup after the big wins.

If you want a second pair of eyes on a specific store, our e-commerce engineering team does these audits as a fixed-scope engagement. But honestly, most of this you can ship yourself before the end of the week.

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

Want a team like ours?

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

Start a project