Shopify serves WebP automatically — here's how the CDN works, what theme code you control, and how to optimise product imagery for speed.
Shipping WebP on a Shopify Store
Shopify is the easiest major platform to ship WebP on. Every image uploaded to a Shopify store is automatically served as WebP to capable browsers via the Shopify CDN — no plugin, no theme code, no configuration. The interesting work on Shopify is downstream: making sure your theme requests the right sizes, lazy-loads the right images, and preloads the LCP image.
For the underlying performance principles, see WebP Optimisation. For the loading strategy that matters most on product pages, see WebP Lazy Loading.
What Shopify does automatically
Shopify's image CDN (cdn.shopify.com) handles four things for every image uploaded through Files, products, collections, or theme settings:
- Format negotiation. Browsers that send
Accept: image/webpreceive a WebP file. Browsers that don't receive the original format (typically JPEG). The single URL transparently returns the right format. - On-the-fly resizing. Append URL parameters like
?width=800&height=600to get a resized variant. Shopify generates and caches it on first request. - Quality adjustment. The CDN serves WebP at a tuned quality setting that produces 25–35% smaller files than the JPEG equivalent.
- Aggressive caching. Images carry a long
Cache-Controlso browsers and CDNs hold variants for extended periods.
You inherit all of this for free. The remaining work is theme-side.
The Shopify image-URL filter system
Modern Shopify themes generate image URLs through Liquid filters. The most important:
{{ product.featured_image | image_url: width: 800, height: 800 }}
This produces a URL like https://cdn.shopify.com/.../image_800x800.jpg?v=1234567890&width=800. The CDN serves WebP automatically when the requesting browser supports it.
For responsive images:
<img
src="{{ product.featured_image | image_url: width: 800 }}"
srcset="
{{ product.featured_image | image_url: width: 400 }} 400w,
{{ product.featured_image | image_url: width: 800 }} 800w,
{{ product.featured_image | image_url: width: 1200 }} 1200w,
{{ product.featured_image | image_url: width: 1800 }} 1800w
"
sizes="(max-width: 768px) 100vw, 800px"
alt="{{ product.featured_image.alt | escape }}"
width="{{ product.featured_image.width }}"
height="{{ product.featured_image.height }}"
loading="lazy"
decoding="async"
/>
Note: no <picture> element required. Shopify's content negotiation handles WebP vs JPEG without it. The single <img> with srcset is the full pattern.
Theme-side optimisations that matter
The LCP product image
On a product page, the featured product image is almost always the LCP element. Two changes:
- Don't lazy-load it. Most Shopify themes apply
loading="lazy"to all product images including the first one. Override for the LCP image:
{% for media in product.media %}
{% if forloop.first %}
<img
src="{{ media | image_url: width: 1200 }}"
srcset="..."
sizes="..."
alt="{{ media.alt }}"
width="{{ media.width }}"
height="{{ media.height }}"
fetchpriority="high"
decoding="async"
/>
{% else %}
<img
src="{{ media | image_url: width: 1200 }}"
...
loading="lazy"
decoding="async"
/>
{% endif %}
{% endfor %}
- Preload it from
<head>. Add a preload link intheme.liquid:
{% if template contains 'product' and product.featured_image %}
<link
rel="preload"
as="image"
href="{{ product.featured_image | image_url: width: 1200 }}"
imagesrcset="
{{ product.featured_image | image_url: width: 800 }} 800w,
{{ product.featured_image | image_url: width: 1200 }} 1200w,
{{ product.featured_image | image_url: width: 1800 }} 1800w
"
imagesizes="100vw"
fetchpriority="high"
/>
{% endif %}
Preload exactly one image per page. See Core Web Vitals & Images for why.
Product galleries
A typical product page has 4–10 images. The first is the LCP; the rest should lazy-load with loading="lazy". Most themes get this right but a few patterns to watch:
- Carousel pre-rendering. Some themes load all gallery images at page load even though only the first is visible. Check the network panel — only the active image should request initially.
- Hover swap. Some themes pre-load the "alternate" product image (revealed on hover) at page load. This is wasted bandwidth on most visits.
- Zoom variants. If your theme offers click-to-zoom, the zoom image is often a higher-resolution variant. Don't load it until the user interacts.
Collection page image sizes
Collection pages render dozens of product thumbnails. The default thumbnail size in most themes is larger than needed:
- A 200×200 displayed thumbnail does not need a 800×800 source.
- A 400×400 displayed thumbnail does not need a 1200×1200 source.
Audit the actual rendered sizes (Chrome DevTools → Inspect → Computed → check width and height) and request the right size from the image filter. Halving the thumbnail dimensions roughly quarters the file size.
Header logo
The header logo appears on every page. If it's larger than needed:
<img
src="{{ shop.brand.logo | image_url: width: 240 }}"
width="240"
height="80"
alt="{{ shop.name }}"
fetchpriority="high"
/>
Note fetchpriority="high" — for ecommerce, the logo is often a brand trust signal that should render immediately.
Custom CDN or app-level overrides
Some Shopify Plus merchants override the default Shopify CDN with a custom CDN configuration. If you do:
- Verify your custom CDN preserves the
Acceptheader content negotiation. - Verify it sets
Vary: Acceptso cached variants don't leak across browser types. - Most major CDNs (Cloudflare, Fastly) handle this when configured correctly.
For most stores, the default Shopify CDN is faster and simpler than rolling your own.
Image-heavy app considerations
Apps that inject images outside the standard product/collection templates often bypass Shopify's image filters:
- Review apps loading user-submitted product photos.
- Visual search apps loading thumbnails for similar products.
- Marketing apps loading custom hero imagery.
For each, verify the images are coming from cdn.shopify.com (or your CDN) and are using the WebP-capable URL pattern. App-injected images from third-party hosts often miss out on WebP serving.
Lighthouse and Core Web Vitals
After theme tweaks, audit:
- Lighthouse score — should not flag "serve images in next-gen formats" if Shopify CDN is serving correctly.
- LCP — should be under 2.5s on product pages. If not, the LCP image is the place to start.
- CLS — ensure every
<img>has explicitwidthandheightattributes. Theme defaults usually do this; custom templates may not.
For the full performance framework, see Core Web Vitals & Images.
What doesn't apply to Shopify
Several common WebP optimisations from other platforms are unnecessary on Shopify:
- Plugin-based conversion — not needed; the CDN handles it.
<picture>element fallbacks — not needed;Accept-header negotiation replaces it.- Bulk conversion of existing images — not needed; the CDN converts on first request.
- CSS background image conversion — Shopify's CDN handles content negotiation for images served from
cdn.shopify.com, including CSS backgrounds.
This makes Shopify by far the easiest major platform to ship WebP on. The performance work is in theme code, not in conversion infrastructure.
Common issues
"Network panel shows JPEG, not WebP"
The browser sent Accept without image/webp. Check the User-Agent — if you're testing in an unusual browser, it may not advertise WebP support. In Chrome and any current major browser, WebP is requested by default.
"Custom CDN is serving the wrong format"
If you've configured a CDN in front of Shopify's CDN, it may not be passing the Accept header through. Reconfigure to forward the header on image requests.
"LCP image isn't being preloaded"
Verify the <link rel="preload"> is in <head> before any scripts that delay parsing. Some apps inject scripts that delay HTML parsing of the preload tag.
"Lazy loading skips the cart drawer images"
If your theme uses a slide-out cart that loads on click, the images inside it can be lazy-loaded — but they need IntersectionObserver-based lazy loading rather than loading="lazy", because they may not be in the viewport when the page loads but appear in the viewport when the drawer opens. See WebP Lazy Loading.
Recommended setup
For a typical Shopify store in 2026:
- Use the default Shopify CDN (don't override).
- Use the modern
image_urlfilter throughout the theme (not the deprecatedimg_url). - Apply
srcsetandsizeson every product image. - Apply
loading="lazy"to all images except the LCP candidate on each template. - Apply
fetchpriority="high"to the LCP image and preload it from<head>. - Audit collection-page thumbnail dimensions and request the smallest size that meets retina display needs.
Where to go from here
- WebP Optimisation — performance fundamentals
- WebP Lazy Loading — loading strategy detail
- Core Web Vitals & Images — measuring impact
- WebP Browser Support — fallback patterns
- Image SEO Best Practices — image-search visibility
- Convert content: PNG to WebP, JPG to WebP
Shopify takes WebP off your plate at the infrastructure level. The remaining performance work is in disciplined theme code — and that work pays back across every product view.