On most Shopify stores, the slowest thing a shopper waits for is the hero image or the first product photo. That moment is Largest Contentful Paint (LCP), and Google wants it under 2.5 seconds on mobile.
The good news: most slow LCP on Shopify comes from a handful of theme habits, not from Shopify itself. Each fix below is a small Liquid or HTML change you can make in an afternoon.
Measure first. Before you change anything, record three numbers:
- PageSpeed Insights (mobile) for your home page and one product page. Note the LCP and which element it says is the LCP element.
- Shopify admin → Online Store → Themes → Web performance. This shows LCP from real visitors, which is what Google actually uses.
- A DevTools Performance recording on a throttled mobile profile, so you can see what loads before the hero image.
Work on a duplicate of the live theme, change one thing at a time, and re-measure after each change.
Fix 1: Make the hero a real image, not a CSS background
A hero set with background-image in CSS is invisible to the browser's preload scanner. The browser only finds it after it downloads and parses the stylesheet, which can add a second or more on mobile.
{%- comment -%} Before: discovered late {%- endcomment -%}
<div class="hero" style="background-image: url({{ section.settings.image | image_url: width: 2000 }})"></div>
{%- comment -%} After: discovered in the first HTML bytes {%- endcomment -%}
<div class="hero">
{{ section.settings.image | image_url: width: 1500 | image_tag: class: 'hero__img' }}
</div>
Use object-fit: cover on .hero__img to keep the same look as a background.
Fix 2: Never lazy-load what is above the fold
Many themes put loading="lazy" on every image. On the hero, that tells the browser to wait, which is the opposite of what you want. Use section.index so only sections lower on the page lazy-load:
{%- liquid
if section.index == 1
assign loading = 'eager'
assign priority = 'high'
else
assign loading = 'lazy'
assign priority = 'auto'
endif
-%}
{{ section.settings.image
| image_url: width: 1500
| image_tag: loading: loading, fetchpriority: priority }}
Apply the same idea on product pages: the first product image is eager with fetchpriority="high"; the rest of the gallery stays lazy.
Fix 3: Serve the right size for each screen
A single width: 2000 image is sent to a 375 px phone too. Give the browser a choice with widths and tell it how wide the image will display with sizes:
{{ section.settings.image
| image_url: width: 1800
| image_tag:
widths: '375, 550, 750, 1100, 1500, 1800',
sizes: '100vw',
loading: loading,
fetchpriority: priority }}
If the image sits in a half-width column on desktop, say so: sizes: '(min-width: 990px) 50vw, 100vw'. Shopify's CDN also serves WebP or AVIF automatically when the browser supports it, so you don't need to convert files yourself.
Fix 4: Preload the one image that matters
For the LCP image only, image_tag can add a preload hint, so the download starts before the browser reaches the <img> tag. Combine it with the settings from Fix 3 on the same image:
{%- if section.index == 1 -%}
{{ section.settings.image
| image_url: width: 1800
| image_tag: preload: true, fetchpriority: 'high',
widths: '375, 550, 750, 1100, 1500, 1800', sizes: '100vw' }}
{%- endif -%}
Preload one image per page, not five. Every extra preload competes for the same bandwidth and can make LCP worse.
Fix 5: Defer theme scripts and remove app leftovers
Any <script> in <head> without defer or async stops the page from rendering until it downloads and runs. Add defer to theme scripts that don't need to run first:
<script src="{{ 'global.js' | asset_url }}" defer></script>
Then look for scripts from apps you no longer use. Uninstalling an app does not always remove the code it added to the theme. Search the theme code for the app's name, and check theme.liquid and the snippets folder for old includes. For apps you still use, turn off any app embed (Theme editor → App embeds) that isn't needed on every page.
You can't edit what content_for_header loads, but you can control everything else, and on most stores that is where the waste is.
Fix 6: Load fonts without blocking text
Custom fonts often hold up the first paint. If you use theme font settings, load them with font_display: 'swap' so text shows immediately in a fallback font:
<style>
{{ settings.type_body_font | font_face: font_display: 'swap' }}
{{ settings.type_header_font | font_face: font_display: 'swap' }}
</style>
Preload only the one font file used above the fold, usually the heading font:
<link rel="preload" as="font" type="font/woff2"
href="{{ settings.type_header_font | font_url }}" crossorigin>
Use two font families at most, and fewer weights. Each extra weight is another file to download.
Fix 7: Load CSS only where it is used
Many themes load one large theme.css on every page, even styles for sliders, popups and product tabs that the home page never shows. Keep the base CSS in theme.liquid small, and load component CSS inside the section that needs it:
{%- comment -%} sections/featured-slider.liquid {%- endcomment -%}
{{ 'component-slider.css' | asset_url | stylesheet_tag }}
The browser then downloads slider styles only on pages that actually have a slider. Use the Coverage tab in Chrome DevTools to find how much of each CSS file goes unused on a page.
Check your results
Re-run PageSpeed Insights after each change, but judge the work by the Web performance report in Shopify admin. Lab scores move instantly; real-visitor LCP takes about 28 days to fully reflect a change.
Quick checklist
- [ ] Hero is an
<img>, not a CSS background - [ ] First section and first product image are
eagerwithfetchpriority="high" - [ ] Images use
widthsandsizes - [ ] Exactly one preloaded image per page
- [ ] Theme scripts use
defer; old app code removed - [ ] Fonts use
font-display: swap; one font preloaded - [ ] Component CSS loads only in the sections that use it
Which of these made the biggest difference on your store? I'd like to hear in the comments.
I'm Raunak Singh, a Shopify and Next.js developer with 7+ years of experience, including Shopify Plus builds for multi-brand retailers. I write about Shopify performance and development at raunaksingh.co.in.
Top comments (0)