Shopify image optimization: the biggest speed win available
Images are the heaviest thing on the page and the most fixable. Most of the work is one Liquid filter used correctly.
7 min read · Performance ·
Images are typically 60–80% of a Shopify page's weight and the single biggest speed lever you have. The good news is that Shopify's CDN does most of the work — if the theme asks it correctly.
Let Shopify generate the markup
The one habit that fixes most of this:
``liquid {{ product.featured_image | image_tag: widths: '400, 600, 800, 1200, 1600', sizes: '(min-width: 768px) 50vw, 100vw', loading: 'lazy', alt: product.title }} ``
image_tag produces a complete <img> with srcset, width, height and modern format negotiation. Hand-writing image tags means reimplementing all of that, usually incompletely.
For more control, image_url gives you the URL alone:
``liquid {{ image | image_url: width: 800 }} {{ image | image_url: width: 800, height: 800, crop: 'center' }} ``
Full filter reference in Liquid filters.
The LCP image is different
The rule that matters most, and the one most often broken:
Never lazy-load the largest above-the-fold image. The hero on the homepage, the first product image on a product page. Lazy-loading it delays the browser's discovery of it, which is a direct LCP regression.
``liquid {{ product.featured_image | image_tag: loading: 'eager', fetchpriority: 'high', widths: '600, 900, 1200' }} ``
And preload it in the head so the browser starts fetching before it parses the body.
Everything below the fold gets loading="lazy".
Dimensions prevent layout shift
Every image needs width and height attributes so the browser reserves the space before the file arrives. image_tag adds them automatically; hand-written tags usually don't.
This is the cheapest CLS fix there is, and it's still missing from most themes.
Size for the container, not the original
A product image displayed at 600px wide should not download at 4000px. The widths parameter plus a correct sizes attribute lets the browser pick.
The sizes attribute is what people get wrong. It describes how wide the image will be at each breakpoint:
`` sizes="(min-width: 1200px) 25vw, (min-width: 768px) 33vw, 50vw" ``
Wrong sizes means the browser chooses a bigger file than it needs, and all the srcset work is wasted.
Formats
Shopify's CDN serves modern formats automatically when the browser supports them. You don't need to convert to WebP yourself, and you shouldn't upload WebP — upload high-quality originals and let the CDN handle delivery.
Before uploading
The part that happens outside the theme, and it matters:
- Consistent dimensions and crop across the catalogue. Inconsistency is what makes a store look assembled rather than curated.
- Reasonable originals. Around 2000–2400px on the long edge is plenty for product photography. A 6000px camera original is wasted bytes in your CDN and slower for merchandisers to work with.
- Strip camera metadata.
- Sensible filenames. They become part of the URL.
- Real alt text, written for the person who can't see the image, not for a keyword.
Background images in CSS
The blind spot. Images set with background-image in CSS don't get srcset, don't get lazy loading, and aren't visible to image_tag.
Use <img> with object-fit: cover instead. You get responsive images and CLS protection, and it's no harder to style.
Video
Heavier than images by an order of magnitude.
- Never autoplay video above the fold. It's the most expensive thing you can put there.
- Use a poster image and load the video on interaction.
- Host on Shopify or a video service, not as a raw file in assets.
Checking your work
- Open a product page in DevTools, Network tab, filter to images.
- Sort by size. Anything over a few hundred KB deserves a look.
- Check the intrinsic size against the displayed size — a big gap means wrong
sizesor nosrcset. - Confirm the LCP image is not lazy-loaded.
- Re-measure with field data after a few weeks.
One filter used correctly, one image not lazy-loaded, and dimensions on everything. That's most of image optimisation, and most stores are missing at least one of the three.
Is this the problem you’re looking at?
Send me the link to your store and a line about what is going wrong. You get a straight answer within one business day — no pitch, no obligation.
mario@clicksandcarts.coOr see what I do around Shopify: services, work beyond the theme, selected work.