speed
Why your Shopify collection page LCP is slow
If you run a Shopify store with more than a handful of collections, the collection page is almost certainly your slowest template — and your worst Core Web Vitals offender. The product page gets all the attention. The collection page gets all the apps.
Most of my speed-optimization work on Shopify ends up touching collection pages, so this is the walk-through I wish someone had handed me five years ago: where to look, what tends to break, and the fixes that consistently move the number.
TL;DR
- LCP on Shopify collection pages is usually the hero / banner image or the first product card image — and it’s slow because of how the theme tells the browser to fetch it, not because of how big it is.
- The cheapest wins, in order:
fetchpriority="high"on the LCP image · propersrcsetandsizes· deferring above-the-fold app blocks · self-hosting the heading font. - Don’t optimize what isn’t the LCP element. Open the Performance panel, find the actual element, and fix that one.
Step 0: Confirm what your LCP element actually is
Before anything else, open Chrome DevTools → Performance Insights (or Performance → record load → look at the LCP marker). The LCP element is not always what you think it is. On stores I’ve audited, the LCP has turned out to be:
- The hero banner image
- The first product card image (when there’s no banner)
- The H1 heading text (when the font is render-blocked)
- The collection description paragraph (rare — usually a font issue)
- A promo bar that’s actually painting first because of layout shift
The fixes are completely different for each. Don’t skip this step.
You can also check this with PageSpeed Insights — under “Diagnostics → Largest Contentful Paint element” it tells you the exact selector.
Step 1: Measure on field data, not just lab
PSI gives you a Lighthouse lab score and a real-user CrUX score (when there’s enough traffic). Trust the CrUX number. Lab scores are run from a single location with a fixed network throttle. Real users are on a phone in a coffee shop on hotel WiFi — and that’s the experience Google ranks on.
My default measurement set, every time:
- Lighthouse mobile (lab) — directional only
- CrUX 28-day p75 (field) — the number Google ranks on
- WebPageTest 4G run from your top customer region — gives you a real waterfall
If the lab and field numbers diverge by more than 30%, the problem is something you only see under realistic conditions: a third-party app that loads slowly from certain regions, a font fallback that doesn’t trigger on the throttle profile, etc.
The four fixes that actually move the number
1. fetchpriority="high" on the LCP image
This one is free and shockingly effective. By default the browser doesn’t know that your hero
banner is the LCP — it just sees a list of images and downloads them in DOM order, deprioritizing
images marked loading="lazy". If the LCP image is below another image in the DOM, or if the
theme is too aggressive with loading="lazy", LCP gets pushed.
In Liquid, on the section that renders the hero or first product card image:
{{ section.settings.hero_image
| image_url: width: 1600
| image_tag:
loading: 'eager',
fetchpriority: 'high',
width: 1600,
height: 900,
sizes: '100vw'
}}
Two things to notice:
loading: 'eager'— notlazy. Eager is the default, but many themes set lazy on every image globally and it overrides this.fetchpriority: 'high'— tells the browser to start the request before the parser is done, ahead of other resources.
If your theme uses an image-element.liquid snippet, add a priority flag:
{% liquid
if priority
assign loading_attr = 'eager'
assign fetchpriority_attr = 'high'
else
assign loading_attr = 'lazy'
assign fetchpriority_attr = 'auto'
endif
%}
And then in your collection-banner.liquid: {% render 'image-element', priority: true %}.
2. Real srcset and sizes on the LCP image
Shopify’s image_tag filter generates a srcset for you — but only if you give it widths. Don’t
do this:
{{ image | image_url: width: 1600 | image_tag }}
Do this:
{{ image
| image_url: width: 1600
| image_tag:
widths: '375, 750, 1100, 1500, 1920',
sizes: '(min-width: 990px) 100vw, 100vw',
loading: 'eager',
fetchpriority: 'high'
}}
The widths parameter is the one most themes miss. Without it the browser downloads the 1600w
image even on a 375px iPhone — a 4–8× LCP regression on mobile, which is exactly where Google is
measuring you.
3. Defer app blocks that aren’t actually visible above the fold
Online Store 2.0 themes let merchants drop “app blocks” into sections. That’s a great feature right up until the collection page has six app blocks above the fold and each one ships its own JS bundle. (I’ve seen worse. Don’t ask.)
Open Theme Inspector (Theme Customizer → ⌘I) and look at the Liquid render times for each block. Anything over 50ms is suspicious. Anything over 200ms is killing your TTFB.
For app blocks that don’t need to render before LCP, the cheapest fix is to defer their JS:
<!-- in theme.liquid or in a layout, only for collection templates -->
{% if template contains 'collection' %}
<script>
// Defer non-critical app scripts until after first interaction or 3s
window.addEventListener('load', () => {
requestIdleCallback(() => {
document.querySelectorAll('script[data-defer]').forEach((s) => {
const replacement = document.createElement('script');
[...s.attributes].forEach((a) => replacement.setAttribute(a.name, a.value));
replacement.removeAttribute('data-defer');
replacement.text = s.text;
s.replaceWith(replacement);
});
});
});
</script>
{% endif %}
The harder fix — and the right long-term answer — is to talk to the merchant about which apps actually earn their slot above the fold. Most don’t.
4. Self-host the heading font
If your LCP is the H1 text (common when the hero is text-only), the font is your bottleneck. Two issues compound:
- Google Fonts forces a TLS handshake to
fonts.googleapis.comandfonts.gstatic.com— two separate origins, two DNS lookups, two connections. font-display: swapmeans the browser paints fallback text first, then swaps. The “paint” that Lighthouse measures is the swapped one if it happens within the LCP window.
Fix: self-host the font, preload it, and serve it from the same origin as your HTML.
In Liquid, upload the font to your theme assets and:
<link rel="preload"
href="{{ 'inter-variable.woff2' | asset_url }}"
as="font"
type="font/woff2"
crossorigin>
<style>
@font-face {
font-family: 'Inter';
src: url("{{ 'inter-variable.woff2' | asset_url }}") format('woff2-variations');
font-weight: 100 900;
font-display: swap;
}
</style>
font-display: swap is still right — it just means the LCP measurement triggers on the swap.
Self-hosting cuts ~150–400ms off mobile LCP from the connection savings alone.
What I do not recommend
A few things that come up in every other Shopify performance article and that I wouldn’t bother with — or would actively avoid:
- “Just use a faster theme.” Themes don’t have inherent speed; the configuration does. Dawn with too many app blocks is slower than a custom theme with none.
- AMP for collection pages. Google deprecated AMP signals; Shopify deprecated AMP support. Don’t.
- Lazy-loading every image, including the LCP. Most common own-goal I see in audits —
loading="lazy"on the hero image directly tanks LCP, and most theme-store themes ship with it set globally. - Preconnecting to every third party. The browser only honors a few preconnects; spamming
<link rel="preconnect">to ten origins makes things slower, not faster.
A simple weekly check
Once you’ve shipped fixes, the goal is to keep the score from regressing. The minimum viable monitoring:
- Subscribe to your store’s CrUX history report
in a spreadsheet. Weekly p75 LCP, INP, CLS for
mobile. - A WebPageTest scheduled run on the collection page, every Monday morning.
- A Search Console alert on Core Web Vitals issues.
If any of those turn red, that week’s “deploy a new app” or “drop in a new section” is the suspect. Roll back, measure, decide.
If you want me to look at your collection page specifically, bring the URL to a free intro call. I’ll run a Lighthouse + CrUX check live, point at your actual LCP element, and tell you which of the four fixes above would move the most for your store.
Found this useful? Get in touch or subscribe to the RSS feed .