Images are the largest byte block in modern online shops — and almost always the LCP element. With AVIF you save around 50 % versus JPEG and 20-30 % versus WebP at comparable visual quality (Cloudinary/Smashing Magazine). But the format alone is not enough: lazy-loading the LCP image moves the 75th percentile from 364 ms to 720 ms and drops the share of "good" pages from 79 % to 52 % (Web Almanac 2025/web.dev). In this technical guide we build an adaptive image pipeline using picture, srcset, loading, fetchpriority, Vary headers and edge resizing — clean enough for Shopware shops targeting Lighthouse scores close to 100 without losing visual quality.
Format comparison: JPEG vs WebP vs AVIF
The three dominant web formats differ mainly in compression efficiency and browser support. JPEG sits at around 2.0 bits per pixel in the median web sample, WebP at 1.3 and AVIF at 1.4 (Web Almanac 2024). In practice WebP compresses about 25-34 % better than JPEG at equivalent visual quality (Cloudinary/ShortPixel), AVIF reaches -50 % versus JPEG and -20 to -30 % versus WebP at comparable visual quality (Cloudinary/Smashing Magazine/Crystallize). The catch with AVIF: encoding times are 5-10x higher than JPEG/WebP depending on the tool. Pure on-demand conversion without caching rarely makes sense.
| Property | JPEG | WebP | AVIF |
|---|---|---|---|
| Bits per pixel (median web) | 2.0 | 1.3 | 1.4 |
| Byte savings vs JPEG | 0 % | -25 to -34 % | -50 % |
| Byte savings vs WebP | +33 % | 0 % | -20 to -30 % |
| Lossless mode | No | Yes | Yes |
| Alpha channel | No | Yes | Yes |
| Animation | No | Yes | Yes |
| Browser support 2026 | 100 % | ~97 % | ~94 % |
| Encoding speed | Very fast | Fast | Slow |
| HDR support | No | No | Yes |
According to the Web Almanac 2024 AVIF grew by +386 % between 2022 and 2024, but still accounted for only 1 % of all web images by the end of 2024, while WebP held 12 % and JPEG dropped from 40 % to 32 % (Web Almanac 2024). Anyone moving to AVIF today is not a laggard — the median image size in the 50th percentile is 12 KB, in the 75th 56 KB, and in the 90th 177 KB (Web Almanac 2024). The upper percentiles benefit disproportionately from AVIF.
Using picture and srcset properly
The picture element is the central building block of adaptive image loading: browsers automatically pick the best supported format. Order matters — AVIF first, then WebP, JPEG as fallback in the img tag. With srcset and sizes the browser additionally picks the right resolution per device class. On smartphones this saves 70-90 % of bytes versus desktop delivery (web.dev). Combined with format negotiation realistic savings of up to 75 % image payload on mobile are common.
<picture>
<source
type="image/avif"
srcset="/img/hero-400.avif 400w,
/img/hero-800.avif 800w,
/img/hero-1200.avif 1200w,
/img/hero-1600.avif 1600w"
sizes="(max-width: 768px) 100vw, 50vw">
<source
type="image/webp"
srcset="/img/hero-400.webp 400w,
/img/hero-800.webp 800w,
/img/hero-1200.webp 1200w,
/img/hero-1600.webp 1600w"
sizes="(max-width: 768px) 100vw, 50vw">
<img
src="/img/hero-800.jpg"
srcset="/img/hero-400.jpg 400w,
/img/hero-800.jpg 800w,
/img/hero-1200.jpg 1200w,
/img/hero-1600.jpg 1600w"
sizes="(max-width: 768px) 100vw, 50vw"
width="1600" height="900"
alt="Sample product image"
loading="eager"
fetchpriority="high"
decoding="async">
</picture>Three details make the difference: first, always set width and height — otherwise the layout jumps and CLS suffers. Second, describe sizes realistically, otherwise the browser loads variants that are too large or too small. Third, do not drop the JPEG fallback even though AVIF/WebP work nearly everywhere — some bot user agents and old in-app browsers still don't understand the modern formats.
Lazy loading: when yes, when never
Native loading="lazy" has been supported since Chrome 77 (09/2019), Firefox 75 and Safari 15.4 — coverage in 2026 is around 95 % (caniuse). The temptation is to lazy-load every image, which is the most common LCP killer in Shopware front-ends. According to the Web Almanac 2025, 10.4 % of mobile pages lazy-load their LCP image natively + 5.9 % via JavaScript — roughly one in six pages (Web Almanac 2025). These pages typically lose 200-500 ms LCP in the 75th percentile and fall from 79 % to 52 % "good" (web.dev).
Above-the-fold hero, product image on the PDP, banner in the carousel first slide: these images MUST use loading="eager" (or no loading attribute) and ideally fetchpriority="high". Use lazy loading only below the initial viewport — typically from the third listing element onward or below the first 1000 px scroll depth.
A robust pattern: first 3-6 images eager, everything below lazy. For carousels load only the active slide eager and lazy-load all others. If you currently use a JavaScript-based lazy-loader, switching to the native attribute is usually worth it — simpler, more reliable and without layout flicker.
fetchpriority and preload for the LCP hero
fetchpriority="high" tells the browser to prioritize an image — even before non-critical CSS or JS subresources. Google Flights cut LCP from 2.6 s to 1.9 s with this hint (-700 ms), Etsy reported +4 % LCP improvement, and lab tests showed up to 20-30 % effect (web.dev/Chrome DevRel). Support spans Chrome, Edge, Safari and Firefox (since October 2024) and is at near-universal coverage in early 2026 (MDN).
<!-- In <head>: preload the AVIF variant of the LCP image -->
<link
rel="preload"
as="image"
href="/img/hero-1200.avif"
imagesrcset="/img/hero-800.avif 800w,
/img/hero-1200.avif 1200w,
/img/hero-1600.avif 1600w"
imagesizes="(max-width: 768px) 100vw, 50vw"
type="image/avif"
fetchpriority="high">
<!-- In <body>: the image itself -->
<picture>
<source type="image/avif" srcset="..." sizes="...">
<img src="/img/hero-fallback.jpg"
fetchpriority="high"
loading="eager"
decoding="async"
width="1600" height="900"
alt="Hero product image">
</picture>Important: at most one image per page with fetchpriority="high" — otherwise the effect dissipates because the browser can no longer prioritize unambiguously. On listing pages this is typically the first product image top-left, on PDPs the large gallery hero. A simple test: in Chrome DevTools network panel the LCP image should show priority "High" and appear among the first loaded resources.
Art direction with picture-source-media
While srcset delivers the same crop in different resolutions, the media attribute on source enables real art direction: different crops or even motifs per viewport. Classic example: a portrait-oriented hero on mobile, a wide-format hero on desktop. This saves additional bytes on mobile because unimportant background is cropped away rather than just scaled.
<picture>
<!-- Mobile: 4:5 portrait, focused on the product -->
<source
media="(max-width: 640px)"
type="image/avif"
srcset="/img/hero-mobile-400.avif 400w,
/img/hero-mobile-800.avif 800w">
<!-- Tablet: 1:1 square -->
<source
media="(max-width: 1024px)"
type="image/avif"
srcset="/img/hero-square-800.avif 800w,
/img/hero-square-1200.avif 1200w">
<!-- Desktop: 16:9 wide with lifestyle -->
<source
type="image/avif"
srcset="/img/hero-wide-1200.avif 1200w,
/img/hero-wide-1600.avif 1600w">
<img src="/img/hero-wide-1200.jpg"
width="1600" height="900"
alt="Product in lifestyle context">
</picture>To manage the crops in the back-end, use an image service with defined crop hot-spots. Shopware supports this natively via media-crop definitions, similar to headless CMS setups in Vue/Nuxt frontends.
Edge resizing and CDN format negotiation
Generating every variant (AVIF/WebP/JPEG x several widths x crop variants) statically in the build pipeline quickly leads to thousands of files per product. Edge resizing is more elegant: the CDN converts and scales on-demand based on URL parameters and the browser's Accept header. According to Cloudflare and Gumlet such pipelines save 60-80 % of "wasted bytes" on mobile — pixels the user never sees (Cloudflare/Gumlet).
| Aspect | Build pipeline (static) | Edge resizing (on-demand) |
|---|---|---|
| Initial effort | High (generate all variants) | Low (define URL schema) |
| Storage need | Very high (all crops x formats) | Low (original + cache) |
| Format negotiation | Manual via picture-source | Automatic via Accept header |
| New crop variant | Full re-build cycle | Instant via URL parameter |
| TTFB on first request | Very fast (static) | Slower (conversion) |
| TTFB after second request | Very fast | Very fast (edge cache) |
| Quality control | Per file | Via parameters / profiles |
| Suited for | Marketing hero, logos | Product images, UGC, dynamic content |
In practice a hybrid strategy is often the most robust: deliver critical LCP images as static AVIF/WebP variants, route all other product images through edge resizing on-demand. Hosting for such setups should support HTTP/2 or HTTP/3, Brotli and well-configured edge caching — see our hosting solutions and cloud consulting.
Vary header and the right caching strategy
If you serve different image formats based on the Accept header you MUST signal that to all caches via Vary: Accept. Otherwise a CDN may serve a cached AVIF image to browsers that don't understand the format — resulting in broken product images. Cloudflare additionally recommends Vary: Accept, Save-Data for Save-Data-aware pipelines (Cloudflare Blog).
# Cache images from the image endpoint correctly
location ~* ^/media/.+\.(jpg|jpeg|png|webp|avif)$ {
add_header Cache-Control "public, max-age=31536000, immutable";
add_header Vary "Accept, Save-Data, DPR";
add_header X-Content-Type-Options "nosniff";
expires 1y;
access_log off;
}
# When edge resizing generates the image on-demand:
location /img/ {
proxy_pass https://image-service.example.com;
proxy_cache_key "$scheme$request_method$host$request_uri$http_accept";
proxy_cache_valid 200 30d;
add_header Vary "Accept";
}Note: Vary: User-Agent is usually NOT a good substitute, as it fragments the cache (each browser version becomes its own cache entry). Accept is significantly more compact and exactly what's intended for this. On Cloudflare/Plesk setups the Vary header must be explicitly allowed so it doesn't get stripped — see Cloudflare docs on "cacheable Vary".
Build pipeline with libvips and Sharp
Several open-source options exist for conversion in build or image-service contexts. libvips and the Node binding Sharp are reportedly 4-5x faster than ImageMagick at roughly 200 MB versus 3 GB RAM for large images (sharp.pixelplumbing.com/Criteo). Squoosh CLI is well suited for individual images and marketing hero conversion. For Shopware setups, Sharp in a background worker or CDN worker is recommended because it natively supports AVIF encoding via libaom.
Rules of thumb for encoding parameters: AVIF Q=50-60 is usually visually indistinguishable from Q=80 — anything higher costs bytes without visible gain. WebP Q=75-80 roughly matches JPEG Q=85. Set effort/CPU-use to 4-6 — higher values yield <2 % savings at significantly longer encoding times, unsuitable for on-demand workloads.
Browser support 2026: what still needs to be compatible?
| Format / Feature | Chrome | Edge | Firefox | Safari | Global coverage |
|---|---|---|---|---|---|
| AVIF | 85+ (08/2020) | 85+ | 93+ (10/2021) | 16.1+ (10/2022) | ~94 % |
| WebP | 23+ | 18+ | 65+ | 14+ | ~97 % |
| loading="lazy" | 77+ | 79+ | 75+ | 15.4+ | ~95 % |
| fetchpriority | 101+ | 101+ | 132+ (10/2024) | 17.2+ | ~93 % |
| decoding="async" | 65+ | 79+ | 63+ | 11.1+ | ~98 % |
| Save-Data header | Yes | Yes | No | No | ~70 % |
Practical takeaway: AVIF + WebP + JPEG fallback in picture covers nearly all relevant browsers. loading="lazy" and decoding="async" can be used without concern. fetchpriority is broadly available since Firefox 132 (October 2024) — if you still need to support older Firefox versions, add rel="preload" resource hints to secure LCP. Coverage data from caniuse.com and RUMvision 2026 (caniuse/RUMvision).
Measurement setup: LCP, p75, Web Vitals
- CrUX data as source of truth: real-user data from the Chrome User Experience Report — the basis for Google rankings, not lab tests.
- Lab tests with Lighthouse (CI integration) for regression detection: typically as a GitHub Action before each deploy. More in our Lighthouse 100 guide.
- RUM tracking via
web-vitals.jsor open-source alternatives: captures LCP, INP, CLS per page and user segment. - LCP-element identification via
PerformanceObserver— store the LCP image URL so you know exactly which asset to optimize. - Mobile-first measurement: the 75th percentile on mobile is the critical metric, since Google primarily scores mobile data (see Core Web Vitals Guide).
- Synthetic monitoring on key paths (homepage, top category, top product, checkout step 1) — daily at fixed times.
Roadmap: from standard JPEG to an AVIF pipeline
- Phase 1 — Audit (week 1): Analyze current image delivery. What share is already WebP/AVIF? Which images are LCP? Which are lazy-loaded that should be eager? Use Lighthouse, WebPageTest and the DevTools performance panel.
- Phase 2 — Format pipeline (week 2-3): Set up build or edge pipeline for WebP and AVIF. Define unified quality profiles (e.g. AVIF Q=55, WebP Q=78). Integrate Sharp/libvips into CI or configure an edge service.
- Phase 3 — Markup refactor (week 3-4): Roll out
pictureelements,srcset,sizesandwidth/heighteverywhere. Keep the JPEG fallback. Add unit tests for correct markup generation in the theme layer. - Phase 4 — LCP optimization (week 4-5): Add
fetchpriority="high"and<link rel="preload">for LCP images. Lazy-loading audit: all above-the-fold images set to eager. Adjust carousel logic. - Phase 5 — Caching & headers (week 5): Set
Vary: Acceptcorrectly, longCache-Control max-agewithimmutable, review CDN configuration. Integrate with existing edge caching. - Phase 6 — Monitoring & iteration (ongoing): Activate web-vitals tracking, monitor p75 LCP weekly. Address regressions immediately. Goal: sustainably below 2.5 s LCP on mobile p75.
This article is based on data and recommendations from: web.dev (Google Chrome Developer Relations), HTTP Archive Web Almanac 2024 + 2025, caniuse.com, Cloudflare Blog, Smashing Magazine, Cloudinary, MDN Web Docs, Sharp/libvips documentation, RUMvision, PicShift, Crystallize, Addy Osmani (Google Chrome), DebugBear, Mozilla Developer Network, ShortPixel and Gumlet. Numbers may vary by sample and time and should be treated as guidance values.
Typically yes, but with judgement. With a median image size of 12 KB at the 50th percentile, AVIF only saves a few KB per image, but in the upper percentiles (56-177 KB) savings of 20-50 % add up. For hero and above-the-fold images AVIF usually pays off directly; for small UI icons the effort often outweighs the benefit.
Typically not. Lazy-loading the LCP image moves the LCP score from 79 % to 52 % "good" (web.dev). Lazy loading only makes sense below the visible viewport. Rule of thumb: first 3-6 images eager, everything below lazy. For carousels load only the first active slide eager.
Usually not. Pure compression saves bytes, but without a picture element and srcset the server delivers the same resolution to every device — so smartphones still load oversized desktop variants. The combination of format, resolution and lazy/eager typically yields up to 75 % less image payload on mobile.
If multiple images are flagged "high" the browser can no longer prioritize unambiguously and the LCP effect typically dissipates. Recommendation: at most one image per page with fetchpriority="high", all others without the attribute or selectively with fetchpriority="low" for unimportant below-the-fold images.
Through proper Vary: Accept header setup and edge format negotiation. The browser sends Accept: image/avif,image/webp,/ and the CDN decides based on this header. Without a Vary header you risk a cached AVIF being served to Safari 14 or older user agents that don't understand the format.
Open-source libraries like libvips and the Node binding Sharp tend to be the most resource-friendly choice — according to maintainer benchmarks 4-5x faster than ImageMagick with significantly lower memory needs. For static marketing hero images, Squoosh CLI also works well. For pure on-demand delivery, edge services via CDN workers are usually more efficient than build pipelines.