WEBPery

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:

  1. Format negotiation. Browsers that send Accept: image/webp receive a WebP file. Browsers that don't receive the original format (typically JPEG). The single URL transparently returns the right format.
  2. On-the-fly resizing. Append URL parameters like ?width=800&height=600 to get a resized variant. Shopify generates and caches it on first request.
  3. Quality adjustment. The CDN serves WebP at a tuned quality setting that produces 25–35% smaller files than the JPEG equivalent.
  4. Aggressive caching. Images carry a long Cache-Control so 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:

  1. 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 %}
  1. Preload it from <head>. Add a preload link in theme.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.

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 Accept header content negotiation.
  • Verify it sets Vary: Accept so 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:

  1. Lighthouse score — should not flag "serve images in next-gen formats" if Shopify CDN is serving correctly.
  2. LCP — should be under 2.5s on product pages. If not, the LCP image is the place to start.
  3. CLS — ensure every <img> has explicit width and height attributes. 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.

For a typical Shopify store in 2026:

  1. Use the default Shopify CDN (don't override).
  2. Use the modern image_url filter throughout the theme (not the deprecated img_url).
  3. Apply srcset and sizes on every product image.
  4. Apply loading="lazy" to all images except the LCP candidate on each template.
  5. Apply fetchpriority="high" to the LCP image and preload it from <head>.
  6. Audit collection-page thumbnail dimensions and request the smallest size that meets retina display needs.

Where to go from here

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.

WebP on WordPress: Plugins & Optimisation Guide

Add WebP to your WordPress site. Native support, plugin choices, automatic conversion, lazy loading, and serving WebP with fallbacks.

WebP in React: Next.js Image & Build Pipelines

Ship WebP in React apps. Next.js Image component, Vite/Webpack pipelines, picture-element patterns, and lazy loading for modern React stacks.

WebP Optimisation: Complete Guide to Faster Image Loading

Optimise WebP images for fast page loads. Practical guide to quality settings, responsive delivery, lazy loading, and Core Web Vitals.

WebP Lazy Loading: Defer Images the Right Way

Lazy load WebP images correctly. Native attribute, IntersectionObserver, priority hints, and the rules that decide which images should never lazy load.