Images are typically the single largest contributor to page weight in any e-commerce store. On a typical Magento 2 product page, unoptimized images can easily account for 60–80 % of total transferred bytes. Fixing that is one of the highest-ROI performance wins available — and it's entirely within your control. This guide walks through three complementary strategies: converting to WebP, enabling lazy loading, and offloading delivery to a CDN.
Why Images Hurt Magento Performance So Much
Magento handles a large number of image variants. Every product image is resized into multiple renditions (thumbnail, small_image, base_image, swatch) via the catalog/product/media pipeline. Without intervention, these are served as JPEGs or PNGs — even when the browser supports more efficient formats.
The knock-on effects:
- LCP (Largest Contentful Paint) suffers when the hero product image is large and uncompressed.
- CLS (Cumulative Layout Shift) spikes when image dimensions aren't declared in the HTML.
- TTI (Time to Interactive) is delayed when off-screen images are loaded eagerly, competing with critical resources.
Let's tackle each piece.
1. Serving WebP Images in Magento 2
What WebP gives you
WebP delivers 25–35 % smaller files than JPEG at equivalent visual quality, and 50–80 % smaller than PNG for images with transparency. For a product catalog with hundreds of images, that's a significant bandwidth saving.
Native WebP support (Magento 2.4.4+)
Since Magento 2.4.4, the core image resizer supports WebP natively. The relevant setting is under Stores → Configuration → General → Web → Url Options → Catalog media URL format. Switch this to Image Optimization Parameters (the hash mode). This activates server-side image processing including WebP conversion.
Via CLI:
bin/magento config:set dev/image/default_adapter GD2
bin/magento config:set dev/image/use_webp_image 1
After enabling, flush the image cache:
bin/magento catalog:images:resize
bin/magento cache:flush
Magento will now serve WebP to browsers that support it, falling back to JPEG/PNG for others via the <picture> element.
Manual WebP conversion with cwebp
If you're on an older Magento version, convert images offline and serve them via an Nginx map block:
# Batch convert all product images
find pub/media/catalog/product -name "*.jpg" -exec sh -c \
'cwebp -q 82 "$1" -o "${1%.jpg}.webp"' _ {} \;
Then in Nginx:
map $http_accept $webp_suffix {
default "";
"~*webp" ".webp";
}
location ~* \.(png|jpg|jpeg)$ {
add_header Vary Accept;
try_files $uri$webp_suffix $uri =404;
}
This zero-overhead approach serves WebP to supporting browsers and falls back gracefully — no PHP involved at request time.
2. Native Lazy Loading
Lazy loading defers off-screen images until the user scrolls near them. Since Chrome 77, Firefox 75, and Safari 15.4, this is supported natively via loading="lazy" — no JavaScript library needed.
Enabling it in Magento
The product image templates live in vendor/magento/module-catalog/view/frontend/templates/product/. The key file for the product page is image_with_borders.phtml.
Create a theme override at:
app/design/frontend/<Vendor>/<Theme>/Magento_Catalog/templates/product/image_with_borders.phtml
Add loading="lazy" to all <img> tags that are not the LCP candidate:
<img
src="<?= $block->escapeUrl($block->getImageUrl()) ?>"
width="<?= $block->escapeHtmlAttr($block->getWidth()) ?>"
height="<?= $block->escapeHtmlAttr($block->getHeight()) ?>"
alt="<?= $block->escapeHtmlAttr($block->getLabel()) ?>"
loading="lazy"
class="<?= $block->escapeHtmlAttr($block->getClass()) ?>"
/>
Important: Do not add loading="lazy" to the first visible product image (the LCP element). The browser needs to start downloading that immediately. Only apply it to gallery thumbnails, related products, and below-the-fold images.
Declaring image dimensions
Always include explicit width and height attributes. Without them, the browser can't reserve space before the image loads, causing layout shift (CLS). Magento's $block->getWidth() and $block->getHeight() already return the configured resize dimensions — use them.
Category listing pages
The category grid template (list.phtml / grid.phtml) often renders 20–40 product thumbnails. Lazy-load all except the first row:
<?php $isFirstRow = ($iterator % $columnCount === 1 && $iterator <= $columnCount); ?>
<img
...
loading="<?= $isFirstRow ? 'eager' : 'lazy' ?>"
/>
3. CDN Configuration for Magento 2
A CDN moves your static assets — images, CSS, JS — to edge nodes close to your visitors. Even with perfectly optimized images, a server in Frankfurt serving customers in Singapore adds 200+ ms of latency per asset. A CDN collapses that to ~20 ms.
Choosing a CDN
For Magento, popular options include Cloudflare (free tier, automatic WebP via Polish), BunnyCDN (~$1/TB, excellent price/performance for self-hosted), Fastly (used by Adobe Commerce Cloud), and AWS CloudFront (1 TB/month free, most flexible). For a self-hosted store, Cloudflare or BunnyCDN are the pragmatic choices.
Configuring Magento to use a CDN URL
In Admin: Stores → Configuration → General → Web → Base URLs (Secure)
Set the media base URL to your CDN subdomain:
https://cdn.yourstore.com/
Or via CLI:
bin/magento config:set web/unsecure/base_media_url https://cdn.yourstore.com/
bin/magento config:set web/secure/base_media_url https://cdn.yourstore.com/
bin/magento cache:flush
Magento will now generate all pub/media URLs pointing at the CDN. Your origin server only serves the assets once; subsequent requests are cached at the edge.
CDN cache headers
Make sure your origin sends proper cache headers so the CDN can cache aggressively:
location ~* \.(jpg|jpeg|png|webp|gif|svg|ico|css|js|woff2)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
Set a long TTL for versioned assets. Magento's static content deployment appends version hashes to CSS/JS, so cache-busting is handled automatically.
Cloudflare Polish (automatic WebP)
If you use Cloudflare, enable Polish (Images → Polish: Lossless or Lossy). It automatically converts and serves WebP to supporting browsers — no server-side changes needed. Combine with Mirage for mobile optimization.
Putting It Together: Expected Gains
Here's what a typical Magento 2 store can expect from implementing all three optimizations:
| Metric | Before | After | Improvement |
|---|---|---|---|
| Total image weight (homepage) | 2.4 MB | 680 KB | −72% |
| LCP | 4.2 s | 1.8 s | −57% |
| CLS score | 0.18 | 0.02 | −89% |
| Edge latency (media assets) | 200+ ms | ~20 ms | −90% |
Quick-Start Checklist
- [ ] Enable WebP in Magento 2.4.4+ via
dev/image/use_webp_image - [ ] Run
bin/magento catalog:images:resizeto regenerate image cache - [ ] Add
loading="lazy"to all non-LCP product images - [ ] Always include explicit
widthandheighton<img>tags - [ ] Set CDN base URL in Magento config
- [ ] Configure long
Cache-Control: immutableheaders on media - [ ] Enable Cloudflare Polish or BunnyCDN Image Optimizer if using those CDNs
Conclusion
Image optimization in Magento 2 is a three-layer problem: format (WebP), loading strategy (lazy), and delivery (CDN). None of these require expensive extensions — they're configuration and template changes any developer can make in an afternoon. Combined, they can cut image-related page weight by 70 %+ and shave seconds off your LCP. In an environment where every 100 ms of load time costs conversion rate, that's not just a technical win — it's a business one.
Start with WebP (lowest effort, highest gain), then tackle lazy loading (quick template edit), and finally wire up a CDN for global edge delivery. Your Lighthouse score — and your customers — will thank you.
Top comments (0)