Magento 2 Product Images Optimization: Resize, Cache and LCP
Product images are usually the heaviest part of a Magento page, and on product pages the main image is often the Largest Contentful Paint element. Getting them right touches several layers: how Magento resizes and caches images, which sizes the theme asks for, how files are compressed, and how the browser loads them. This article goes through each layer for Magento Open Source and Adobe Commerce 2.4.8.
How Magento serves product images
Original uploads live in pub/media/catalog/product, in folders named after the first two characters of the file name. The storefront never shows the original. Templates ask an image helper for an image by an ID from the theme's etc/view.xml, such as category_page_grid or product_page_image_medium. Magento resizes the original to the dimensions of that ID and stores the result under pub/media/catalog/product/cache/, in a folder named after a hash of the resize parameters.
If a resized file does not exist yet when the browser asks for it, the web server passes the request to pub/get.php, which creates it on the fly. This is why the nginx sample configuration routes missing files under /media/ to get.php. It works, but the first request for each image size is slow, and a page with many new images can trigger dozens of resize operations at once.
Define the sizes you actually use
Every image ID in view.xml is a size Magento may generate for every product image. Themes inherited from Luma carry many IDs that a custom theme no longer uses. Others are larger than the space they fill on the page.
Override the sizes in your theme's etc/view.xml to match the real layout:
<?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 module="Magento_Catalog">
<image id="category_page_grid" type="small_image">
<width>360</width>
<height>450</height>
</image>
<image id="product_page_image_medium" type="image">
<width>700</width>
<height>875</height>
</image>
</images>
</media>
</view>
Base sizes on the widest rendering of each slot, considering high density screens. Larger than that is wasted bytes. Also keep the aspect ratio of the IDs consistent with the photos. Mismatched ratios produce white padding around images when keep_frame is on, which is the default.
Pre-generate resized images
To avoid resizing during customer requests, generate the cache ahead of time:
bin/magento catalog:images:resize
It goes through every product image and every image ID of the themes in use. On a large catalog it takes hours and uses a lot of CPU. The --async option publishes the work to a message queue instead, so a consumer processes it in the background:
bin/magento catalog:images:resize --async
bin/magento queue:consumers:start media.storage.catalog.image.resize
Run it after changing view.xml, after a theme change, and after bulk image imports. Do not run it on every deployment. Resized images do not depend on code, so they can be kept between releases on a shared media directory, see zero downtime deployments.
The admin has a Flush Catalog Images Cache button under System, Cache Management. It deletes the whole resized image cache. On a large catalog that means every image is regenerated on demand afterwards. Only use it on purpose.
Compression and file size
Upload settings
Under Stores, Configuration, Advanced, System, Images Upload Configuration you set the JPEG quality Magento uses when saving resized images, and a maximum width and height for uploads. Limiting upload dimensions stops editors from uploading 6000 pixel photos straight from a camera, which are slow to resize and waste storage.
bin/magento config:show system/upload_configuration/jpeg_quality
bin/magento config:show system/upload_configuration/max_width
bin/magento config:show system/upload_configuration/max_height
Image adapter
Magento can resize with GD or with ImageMagick, chosen under Advanced, Developer, Image Processing Settings. That section is only visible in developer mode, but the value applies in production. ImageMagick generally produces better quality at the same file size. Test both with your photos.
Optimize the resized files
Magento does not run lossless optimization on resized images. Tools like jpegoptim and optipng can be run over the cache folder after generation. Keep it on a schedule, and do not touch originals.
find pub/media/catalog/product/cache -name '*.jpg' -newer /var/tmp/last-optimize \
-exec jpegoptim --strip-all --max=82 {} +
touch /var/tmp/last-optimize
Modern formats
Serving WebP or AVIF can reduce image weight a lot. Rather than converting inside Magento, the most maintainable route is to let a CDN or image service convert based on the browser's Accept header. If you do it at the web server level instead, make sure caching varies by format, so a browser that does not support the format never receives it.
Resizing at the CDN
Magento 2.4 has a setting called Catalog media URL format, under Stores, Configuration, General, Web, Url Options. The legacy option uses the hash folders described above. The other option puts resize parameters in the query string of the original image URL, and leaves the actual resizing to an image optimization service in front of Magento, for example Fastly Image Optimization on Adobe Commerce Cloud.
Only switch to the query parameter format if something in front of Magento resizes images. Otherwise browsers get full size originals. CDN choices and setup are covered in CDN with Cloudflare or Fastly.
Loading behavior and LCP
Bytes are half the story. How the browser discovers and prioritizes images decides how fast the page feels.
- Lazy load below the fold. Listing images below the first rows should have
loading="lazy". Check your theme's listing template, since custom themes often lose it. - Never lazy load the LCP image. The main product image and the first row of a category listing should load eagerly. On the product page, add
fetchpriority="high"to the main image. - Width and height attributes. Set them on every image so the browser reserves space and the layout does not shift when images arrive.
- The Luma gallery. Luma's product gallery is built with JavaScript, and the main image can appear only after the gallery widget initializes. Rendering the first image as plain HTML, which is what Hyva does, makes LCP much better.
- Responsive images. A
srcsetwith two or three sizes lets phones download smaller files. This needs extra image IDs inview.xmland template changes, so add it where the gain is measurable, usually on listings and the main product image.
Measure with field data from the Chrome UX report or your own real user monitoring, not just a single lab test. More in Core Web Vitals for Magento 2.
Housekeeping
- Deleting a product does not always delete its files. Over the years
pub/media/catalog/productcollects orphan images. Compare files on disk withcatalog_product_entity_media_gallerybefore removing anything, and keep a backup. - Re-importing images creates duplicates with suffixes like
_1. Only send image columns in imports when images change. - Keep the image cache out of backups that you restore often. It can be regenerated.
FAQ
Why are my Magento product images slow the first time?
The resized version does not exist yet, so get.php creates it during the request. Run catalog:images:resize after theme or image changes so images are ready before customers ask for them.
Where does Magento store resized product images?
Under pub/media/catalog/product/cache, in folders named by a hash of the resize parameters from view.xml. Flushing the catalog images cache deletes that folder.
Can Magento 2 serve WebP images?
The simplest reliable way is to convert at the CDN or image service based on browser support. Converting inside Magento needs an extension or custom code and careful cache handling.
Should I lazy load all product images?
No. Lazy load images below the fold only. The main product image and the first visible row of a listing should load immediately, or LCP gets worse.
I am available for freelance Magento and Adobe Commerce work, including frontend performance and Core Web Vitals. If you need help with this, get in touch.
Related articles
Magento 2 CDN: Cloudflare vs Fastly Setup Guide
Put a CDN in front of Magento 2: Fastly as an edge full page cache, Cloudflare for static and media, real client IP, purging and settings that break things.
Magento 2 Core Web Vitals: How to Fix LCP, INP and CLS
Where Magento 2 Luma stores lose Core Web Vitals and how to fix LCP, INP and CLS: server response, CSS, fonts, images, gallery, third party scripts.
Magento 2 Blackfire Profiling: Pages, CLI and Tests
Profile Magento 2 with Blackfire: probe and agent setup, profiling pages and bin/magento commands, reading call graphs and adding performance assertions.
Magento 2 Cache Types Explained: Clean, Flush, Custom
What each Magento 2 cache type stores, the real difference between cache:clean and cache:flush, invalidated caches, and how to add your own cache type.