Latest posts Visit blog

Images are the largest byte block in modern online shops — and almost always the LCP element. With AVIF you save around 50 % versus JPEG at comparable visual quality (web.dev). But the format alone is not enough: for pages with good LCP the median image load delay at the 75th percentile is 350 ms, for pages that need improvement it is already 720 ms (Web Almanac 2024) — and that is exactly where misplaced lazy loading hits. 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 high Lighthouse scores without losing visual quality.

Format comparison: JPEG vs WebP vs AVIF

The three dominant web formats differ mainly in compression efficiency and browser support. In the median of the web corpus JPEG sits at 2.0 bits per pixel, AVIF at 1.4 and WebP at 1.3 (Web Almanac 2024) — the lower the value, the stronger the compression. Against JPEG, savings of more than 50 % have been measured for AVIF depending on content, encoder and quality target (web.dev). The catch with AVIF: encoding times are considerably higher than for JPEG and WebP. Pure on-demand conversion without caching rarely makes sense.

PropertyJPEGWebPAVIF
Bits per pixel (median web)2.01.31.4
Byte savings versus JPEGmore strongly compressedmore than 50 %
Lossless modeNoYesYes
Alpha channelNoYesYes
AnimationNoYesYes
Browser support worldwide (caniuse)universal96.82 %95.36 %
Encoding speedVery fastFastSlow
HDR supportNoNoYes

According to the Web Almanac 2024 the AVIF share grew by 386 % between 2022 and 2024, but still accounted for only 1.0 % of all web images at 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 is 12 KB at the 50th percentile, 56 KB at the 75th and 177 KB at the 90th (Web Almanac 2024) — the upper percentiles benefit most 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. Smartphones then no longer receive oversized desktop variants; combined with format negotiation the image payload on mobile typically drops considerably.

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 — worldwide support stands at 95.82 % (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 2024, 9.5 % of mobile pages lazy-load their LCP image natively and another 6.7 % use a custom approach — 16 % in total (Web Almanac 2024). The LCP breakdown shows what that costs: with good LCP the median image load delay is 350 ms, with pages needing improvement it is already 720 ms (Web Almanac 2024).

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. In a Chrome team test on Google Flights the LCP dropped from 2.6 s to 1.9 s (web.dev). Worldwide support for the attribute stands at 93.74 % (caniuse); Firefox followed in October 2024.

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. Variants are then created only for widths actually requested — and the number of pixels a device downloads without ever displaying them drops accordingly.

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. If the pipeline also varies by Save-Data, that header belongs in the Vary list as well: Vary: Accept, Save-Data.

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 CDN and Plesk setups the Vary header must be explicitly allowed in the cache configuration, otherwise the intermediate layer strips it.

Build pipeline with libvips and Sharp

Several open-source options exist for conversion in the build or in an image service. libvips and the Node binding Sharp reach 4.5 times the PNG throughput of ImageMagick in the maintainer benchmark while working with noticeably lower memory requirements (Sharp). Squoosh CLI is a good fit for individual images and marketing hero conversion. For Shopware setups Sharp in a background worker or CDN worker is the practical choice, because it encodes AVIF natively 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 typically yield hardly any further savings at significantly longer encoding times and are unsuitable for on-demand workloads.

Browser support 2026: what still needs to be compatible?

Format / FeatureChromeEdgeFirefoxSafariWorldwide support (caniuse)
AVIF85+ (08/2020)85+93+ (10/2021)16.1+ (10/2022)95.36 %
WebP23+18+65+14+96.82 %
loading="lazy"77+79+75+15.4+95.82 %
fetchpriority101+101+132+ (10/2024)17.2+93.74 %
decoding="async"65+79+63+11.1+96.45 %
Save-Data headerYesYesNoNo78.26 %

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. The browser support figures come from caniuse.com.

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 draws on the HTTP Archive Web Almanac 2024 (Media and Performance chapters), on caniuse.com, on web.dev and the MDN Web Docs, and on the documentation of Sharp and libvips. The figures quoted come from samples taken at a fixed point in time and keep changing.

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; at the 75th percentile it is 56 KB and at the 90th already 177 KB (Web Almanac 2024), and that is where savings 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 delays exactly the resource the metric waits for — 16 % of mobile pages with an image-based LCP still do this (Web Almanac 2024). 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. Only the combination of format, resolution and lazy/eager noticeably lowers image payload on mobile devices.

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 — in the maintainer benchmark Sharp reaches 4.5 times the PNG throughput of ImageMagick with noticeably lower memory requirements. 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.