DEV Community

Magevanta
Magevanta

Posted on • Edited on • Originally published at magevanta.com

Image Optimization in Magento 2: WebP, Lazy Loading & Beyond

Images are the single largest payload on most Magento 2 storefronts. A typical product listing page ships 2-4 MB of images, a category page with 36 products easily crosses 10 MB, and PDP galleries can push even higher. When Google's Core Web Vitals penalize you for LCP above 2.5 seconds, every kilobyte matters.

The good news: Magento 2 has built-in tooling for most of this. The bad news: almost nobody configures it properly. Let's fix that.

The Cost of Unoptimized Images

Before diving into solutions, let's quantify the problem. A default Magento 2 install with the Luma theme loads:

  • Product thumbnails: ~50-80 KB each (JPEG, no compression tuning)
  • Category banners: 200-500 KB
  • PDP main images: 150-300 KB
  • Product gallery thumbnails: 30-50 KB each

A category page with 36 products at 70 KB per thumbnail = 2.5 MB just in thumbnails. Serve that over a 4G connection at 10 Mbps and you're looking at 2 seconds of pure image transfer — before rendering, layout, or JavaScript execution.

Now compare with the same page serving WebP at 40 KB per thumbnail: 1.4 MB. That's a 44% reduction with zero visible quality loss.

WebP: The Biggest Win With The Least Effort

Native Magento 2 WebP Support

Since Magento 2.4.4, the platform includes native WebP support through the Magento_Catalog module. When enabled, Magento automatically generates WebP variants alongside JPEG/PNG originals.

To enable it in view.xml (located in your theme's etc/ directory):

<?xml version="1.0"?>
<view xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Config/etc/view.xsd">
    <media>
        <images>
            <image id="product_page_image_medium">
                <type>webp</type>
                <width>700</width>
                <height>700</height>
            </image>
            <image id="category_page_grid">
                <type>webp</type>
                <width>300</width>
                <height>300</height>
            </image>
        </images>
    </media>
</view>
Enter fullscreen mode Exit fullscreen mode

After changing view.xml, regenerate images:

bin/magento catalog:images:resize
Enter fullscreen mode Exit fullscreen mode

The <picture> Element Strategy

Magento's native WebP uses the <picture> element with <source> tags, which gives you progressive enhancement — browsers that support WebP get it, others fall back to JPEG:

<picture>
    <source srcset="/media/catalog/product/image.webp" type="image/webp">
    <source srcset="/media/catalog/product/image.jpg" type="image/jpeg">
    <img src="/media/catalog/product/image.jpg" alt="Product name" loading="lazy">
</picture>
Enter fullscreen mode Exit fullscreen mode

This is the gold standard. The browser negotiates format automatically. No JavaScript polyfills needed.

When Native Isn't Enough

The built-in WebP support has limitations:

  1. No AVIF generation — AVIF offers 20-30% better compression than WebP
  2. No quality tuning per breakpoint — you get one quality setting for all sizes
  3. No automatic re-generation — changing settings requires manual catalog:images:resize

For stores that need more control, a dedicated image optimization tool like hyva-themes/magento2-default-theme (which includes optimized image handling) or a CDN-based solution (Cloudflare Images, Imagekit, imgix) gives you per-breakpoint quality control and automatic AVIF.

Lazy Loading: Free Performance With One Attribute

Native Lazy Loading

Magento 2.4.6+ ships with native loading="lazy" on product images. If you're on an older version, you can add it via a small layout override in your theme's Magento_Catalog/templates/product/list.phtml:

<img src="<?= $escaper->escapeUrl($imageHelper->getUrl($_product, 'category_page_grid')) ?>"
     alt="<?= $escaper->escapeHtml($_product->getName()) ?>"
     loading="lazy"
     width="300"
     height="300" />
Enter fullscreen mode Exit fullscreen mode

The width and height attributes are critical — they prevent Cumulative Layout Shift (CLS) by letting the browser reserve space before the image loads.

What About Above-the-Fold?

Lazy loading above-the-fold images is counterproductive — it delays LCP. The main product image on a PDP and the first row of category thumbnails should use loading="eager" (or simply omit the attribute):

<!-- First product in grid: eager -->
<img src="..." loading="eager" width="300" height="300" fetchpriority="high">

<!-- Rest: lazy -->
<img src="..." loading="lazy" width="300" height="300">
Enter fullscreen mode Exit fullscreen mode

The fetchpriority="high" hint tells the browser to prioritize this image's fetch, which can shave 200-500ms off LCP on PDPs.

LQIP: Low-Quality Image Placeholders

For the ultimate perceived-performance win, use Low-Quality Image Placeholders (LQIP). The idea: render a tiny (20x20px), heavily compressed base64 image inline, then swap it with the real image when it loads:

<img src="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQ..."
     data-src="/media/catalog/product/full-size.webp"
     alt="Product"
     class="lazyload"
     width="300"
     height="300">
Enter fullscreen mode Exit fullscreen mode

A 20x20 JPEG base64 is ~300 bytes. It renders instantly, giving the user a blurred preview while the full image streams in. The visual effect is similar to Medium.com's image loading pattern.

Responsive Images: srcset Done Right

Magento generates multiple image sizes per product, but most themes only reference the largest one and let CSS scale it down. This means a mobile user downloading a 700x700 image that's displayed at 150x150.

Using srcset in Magento

The srcset attribute lets the browser pick the right size:

<img src="/media/catalog/product/700x700.webp"
     srcset="
         /media/catalog/product/150x150.webp 150w,
         /media/catalog/product/300x300.webp 300w,
         /media/catalog/product/500x500.webp 500w,
         /media/catalog/product/700x700.webp 700w
     "
     sizes="(max-width: 768px) 150px, (max-width: 1200px) 300px, 500px"
     alt="Product name"
     loading="lazy">
Enter fullscreen mode Exit fullscreen mode

The sizes attribute is where most implementations go wrong. It must match your actual CSS layout breakpoints. Measure the rendered image width at different viewport sizes using DevTools, then set sizes accordingly.

A wrong sizes attribute is worse than no srcset — the browser downloads the wrong image.

Defining Image Roles in view.xml

Magento's view.xml is where you define the image sizes that feed into srcset. Each image "role" (product_page_image_medium, category_page_grid, etc.) can have multiple variants:

<image id="category_page_grid">
    <type>webp</type>
    <width>300</width>
    <height>300</height>
</image>
<image id="category_page_grid_small">
    <type>webp</type>
    <width>150</width>
    <height>150</height>
</image>
Enter fullscreen mode Exit fullscreen mode

Reference these in your template to build the srcset string dynamically.

CDN-Level Optimization

If you're using Varnish + a CDN (Cloudflare, Fastly, BunnyCDN), you can push image optimization to the edge instead of doing it in Magento.

Cloudflare Images

Cloudflare Images handles resizing, format negotiation, and compression at the edge. You serve one high-quality original, and Cloudflare generates variants on-the-fly:

<img src="https://example.com/cdn-cgi/image/width=300,format=webp,quality=80/media/catalog/product/image.jpg"
     alt="Product">
Enter fullscreen mode Exit fullscreen mode

Pros:

  • No Magento image generation needed
  • Automatic AVIF/WebP negotiation per browser
  • Per-request resizing (no pre-generation step)

Cons:

  • $5/month per 100,000 unique images
  • Adds a dependency on Cloudflare's edge for image delivery

Fastly Image Optimizer

If you're on Magento Cloud, Fastly's built-in Image Optimizer is already available. Enable it in the Fastly configuration panel and configure quality/format settings. Fastly does on-the-fly WebP/AVIF conversion with no code changes.

BunnyCDN

BunnyCDN's Image Optimization is cheaper ($0.005 per 1,000 optimizations) and supports WebP, AVIF, and on-the-fly resizing. The URL structure:

https://bunnyoptimizer.example.com/media/catalog/product/image.jpg?width=300&output=webp&quality=80
Enter fullscreen mode Exit fullscreen mode

JPEG Quality Tuning

Magento's default JPEG quality is 80, which is too high for web thumbnails. Most product images look identical at quality 65-70, and the file size difference is significant:

Quality File Size (300x300) Visual Difference
80 (default) 48 KB Baseline
70 34 KB Imperceptible
65 29 KB Barely noticeable on close inspection
60 24 KB Noticeable on detailed images

To change the quality, modify your theme's view.xml:

<image id="category_page_grid">
    <type>webp</type>
    <width>300</width>
    <height>300</height>
    <quality>70</quality>
</image>
Enter fullscreen mode Exit fullscreen mode

For WebP specifically, quality 70 is the sweet spot. WebP at quality 70 looks as good as JPEG at quality 80, with 30% smaller files.

Caching Optimized Images

Magento stores generated images in pub/media/catalog/product/cache/. These are served statically by Nginx/Varnish, so they're fast — but the first request for each unique size triggers generation, which is slow.

Pre-Warm the Image Cache

After deploying changes to view.xml, pre-generate all images:

bin/magento catalog:images:resize
Enter fullscreen mode Exit fullscreen mode

This is a heavy operation on large catalogs (50K+ products can take 30+ minutes). Run it post-deploy, not during traffic spikes.

Varnish Caching for Images

Ensure your Varnish VCL caches image responses with long TTLs:

sub rule_image {
    if (req.url ~ "\.(webp|jpg|jpeg|png|gif|svg)$") {
        unset req.http.Cookie;
        set req.url = regsub(req.url, "\?.*$", "");
        return (hash);
    }
}

sub vcl_backend_response {
    if (bereq.url ~ "\.(webp|jpg|jpeg|png|gif|svg)$") {
        set beresp.ttl = 30d;
        set beresp.http.Cache-Control = "public, max-age=2592000, immutable";
    }
}
Enter fullscreen mode Exit fullscreen mode

The immutable flag tells browsers they never need to revalidate this URL — a significant win for repeat visitors.

Monitoring Image Performance

Lighthouse + WebPageTest

Run Lighthouse audits on category and PDP pages. Focus on:

  • LCP element: Is it an image? If so, it must load in <2.5s
  • Total image weight: Should be <1 MB for category pages, <500 KB for PDPs
  • CLS: Should be <0.1 — proper width/height attributes prevent this
  • Unused bytes in images: Are you serving 700x700 images in 150x150 slots?

Real User Monitoring (RUM)

Synthetic tests don't capture real-world conditions. Use RUM (Sentry, New Relic Browser, or a custom solution) to track actual LCP percentiles across devices:

// Track LCP for image elements
new PerformanceObserver((entryList) => {
    const entries = entryList.getEntries();
    const lastEntry = entries[entries.length - 1];
    if (lastEntry.element && lastEntry.element.tagName === 'IMG') {
        navigator.sendBeacon('/rum/lcp', JSON.stringify({
            lcp: lastEntry.startTime,
            url: window.location.href,
            imgSrc: lastEntry.element.src,
            connection: navigator.connection?.effectiveType
        }));
    }
}).observe({ type: 'largest-contentful-paint', buffered: true });
Enter fullscreen mode Exit fullscreen mode

This tells you which images are actually causing LCP issues for real users on real connections.

The Optimization Checklist

Before you ship, verify each item:

  1. WebP enabled in view.xml for all image roles
  2. Native lazy loading on all below-the-fold images (loading="lazy")
  3. Eager loading + fetchpriority="high" on LCP images
  4. width and height attributes on every <img> to prevent CLS
  5. srcset with correct sizes matching your layout breakpoints
  6. JPEG quality at 70 or lower for thumbnails
  7. Varnish caching for static images with 30-day TTL
  8. Image cache pre-warmed after every view.xml change
  9. CDN-level format negotiation (AVIF > WebP > JPEG) if using Cloudflare/Fastly/Bunny
  10. RUM monitoring on LCP for image-heavy pages

Conclusion

Image optimization is the highest-ROI performance work you can do on a Magento 2 store. Unlike JavaScript bundling or database optimization — which require deep platform knowledge and carry regression risk — image optimization is mostly configuration. The difference between a default Magento install and a properly configured one is often 3-5 MB per pageview, which translates directly into better Core Web Vitals, higher conversion rates, and lower bounce rates.

Start with WebP and lazy loading. Those two changes alone will cut your image payload by 40-50%. Then layer on srcset, quality tuning, and CDN-level optimization to squeeze out the rest.

Top comments (0)