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.

Image Format Matrix: JPEG vs WebP vs AVIFFile size by format and use case (identical quality level)Use caseJPEGWebPAVIFHero 1600pxAbove-the-fold220 kB165 kB110 kBListing 800pxCategory grid95 kB70 kB48 kBThumbnail 200pxCart, wishlist18 kB14 kB10 kBOG image 1200x630Social sharing145 kB110 kB75 kBBrowser support 2026Chrome 85+AVIFFirefox 93+AVIFEdge 85+AVIFSafari 16.1+AVIF~94 % global(caniuse / RUMvision 2026)LCP Score: Lazy vs Preloaded (p75 Mobile)LCP lazy-loaded720 ms52 % goodLCP preloaded + eager364 ms79 % good~16 % of mobile pages lazy-load their LCP image, losing 200-500 ms (Web Almanac 2025 / web.dev)AVIF byte savings-50 %vs JPEG, -25 % vs WebP(Cloudinary / Smashing Magazine)fetchpriority="high"-700 msLCP Google Flights case(web.dev / Chrome DevRel)Edge resizing-60 to -80 %wasted bytes on mobile(Cloudflare / Gumlet)

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.

PropertyJPEGWebPAVIF
Bits per pixel (median web)2.01.31.4
Byte savings vs JPEG0 %-25 to -34 %-50 %
Byte savings vs WebP+33 %0 %-20 to -30 %
Lossless modeNoYesYes
Alpha channelNoYesYes
AnimationNoYesYes
Browser support 2026100 %~97 %~94 %
Encoding speedVery fastFastSlow
HDR supportNoNoYes

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.

product-hero.html
<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).

Never lazy-load LCP images

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).

head-and-hero.html
<!-- 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.

art-direction.html
<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).

AspectBuild pipeline (static)Edge resizing (on-demand)
Initial effortHigh (generate all variants)Low (define URL schema)
Storage needVery high (all crops x formats)Low (original + cache)
Format negotiationManual via picture-sourceAutomatic via Accept header
New crop variantFull re-build cycleInstant via URL parameter
TTFB on first requestVery fast (static)Slower (conversion)
TTFB after second requestVery fastVery fast (edge cache)
Quality controlPer fileVia parameters / profiles
Suited forMarketing hero, logosProduct 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).

nginx-image-headers.conf
# 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.

Terminal
$ # AVIF from JPEG with libvips (CLI)
$ vips copy hero.jpg hero.avif[Q=55,effort=4]
hero.jpg (1600x900) 245 KB hero.avif (1600x900) 112 KB -54 %
$ # WebP variant
$ vips copy hero.jpg hero.webp[Q=78]
hero.webp (1600x900) 168 KB -31 %
$ # Multiple widths via Sharp (Node)
$ node -e "require('sharp')('hero.jpg').resize(800).avif({quality:55}).toFile('hero-800.avif')"
hero-800.avif written (52 KB)
$ # Squoosh CLI for individual images
$ npx @squoosh/cli --avif '{"cqLevel":33}' hero.jpg
hero.avif (108 KB) written

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 / FeatureChromeEdgeFirefoxSafariGlobal coverage
AVIF85+ (08/2020)85+93+ (10/2021)16.1+ (10/2022)~94 %
WebP23+18+65+14+~97 %
loading="lazy"77+79+75+15.4+~95 %
fetchpriority101+101+132+ (10/2024)17.2+~93 %
decoding="async"65+79+63+11.1+~98 %
Save-Data headerYesYesNoNo~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.js or 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

  1. 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.
  2. 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.
  3. Phase 3 — Markup refactor (week 3-4): Roll out picture elements, srcset, sizes and width/height everywhere. Keep the JPEG fallback. Add unit tests for correct markup generation in the theme layer.
  4. 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.
  5. Phase 5 — Caching & headers (week 5): Set Vary: Accept correctly, long Cache-Control max-age with immutable, review CDN configuration. Integrate with existing edge caching.
  6. 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.
Sources and studies

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.

Tags:#Performance#Image Optimization#AVIF#Core Web Vitals#Lazy Loading