Back to blog

A Developer's Guide to Serving Images in Modern Formats

You know that WebP and AVIF produce smaller files than JPG. The question is how to serve them without breaking anything. The answer involves the `<picture>` element, content negotiation, build pipelines, and a bit of pragmatism about when each approach makes sense.

A

You know that WebP and AVIF produce smaller files than JPG. The question is how to serve them without breaking anything. The answer involves the <picture> element, content negotiation, build pipelines, and a bit of pragmatism about when each approach makes sense.

This guide is for developers who want to implement modern image formats correctly, not just convert a few files.


The Format Landscape in 2026

Format Compression Browser support Best for
JPEG Lossy, 8-bit 100% Legacy fallback, email, social sharing
PNG Lossless, 8-bit, alpha 100% Graphics, screenshots, transparency (when WebP not viable)
WebP Lossy/lossless, 8-bit, alpha, animation 97% Default web format, dynamic pipelines
AVIF Lossy/lossless, 10/12-bit, alpha, HDR 93% Maximum compression, photography, static assets

The practical stack in 2026: AVIF first, WebP fallback, JPEG last resort. This covers 100% of browsers while giving 93% of users the best compression available.


Implementing <picture> with Multiple Sources

The <picture> element is the standard way to serve multiple formats. The browser evaluates <source> elements top to bottom and downloads only the first one it supports:

<picture>
  <source srcset="hero.avif" type="image/avif">
  <source srcset="hero.webp" type="image/webp">
  <img src="hero.jpg" alt="Description" width="1200" height="600">
</picture>

Combine with srcset for responsive images:

<picture>
  <source
    srcset="hero-400.avif 400w, hero-800.avif 800w, hero-1200.avif 1200w"
    sizes="(max-width: 600px) 100vw, (max-width: 1200px) 80vw, 1200px"
    type="image/avif">
  <source
    srcset="hero-400.webp 400w, hero-800.webp 800w, hero-1200.webp 1200w"
    sizes="(max-width: 600px) 100vw, (max-width: 1200px) 80vw, 1200px"
    type="image/webp">
  <img
    src="hero-800.jpg"
    alt="Description"
    width="1200"
    height="600"
    fetchpriority="high"
  >
</picture>

Key points:

  • List AVIF before WebP. The browser picks the first match.
  • The type attribute is mandatory. Without it, the browser cannot determine format support.
  • Always include width and height on the <img> to prevent layout shift (CLS).
  • Use fetchpriority="high" on the LCP image, loading="lazy" on below-fold images.
  • The sizes attribute must be identical across all <source> elements and the <img>.

Server-Side Content Negotiation via Accept Header

Content negotiation serves the right format from a single URL. The browser sends an Accept header listing the formats it supports, and the server responds with the best match.

A typical modern browser sends:

Accept: image/avif, image/webp, image/png, image/jpeg, */*

Nginx configuration:

map $http_accept $webp_suffix {
    default "";
    "~*image/webp" ".webp";
}

map $http_accept $avif_suffix {
    default "";
    "~*image/avif" ".avif";
}

location ~* \.(jpg|jpeg|png)$ {
    # Try AVIF first, then WebP, then the original
    try_files $uri$avif_suffix $uri$webp_suffix $uri =404;
    add_header Vary Accept;
}

Important: always return Vary: Accept when the response depends on the Accept header. Without it, caches and CDNs may serve the wrong format to the wrong browser.

Advantages of content negotiation:

  • Simpler HTML, just a single <img> tag.
  • Works with CSS background-image.
  • No client-side JavaScript needed.

Disadvantages:

  • Requires server configuration.
  • Harder to debug (the same URL returns different content).
  • Does not work with static hosting that does not support try_files or equivalent.

CDN-Level Format Negotiation

Most modern CDNs support automatic image format negotiation, which combines the benefits of content negotiation without requiring origin server changes:

Cloudflare: Polish + WebP/AVIF conversion in the Cloudflare dashboard. Enabled per zone, no code changes needed. Cloudflare reads the Accept header and serves the appropriate format from its cache.

AWS CloudFront + Lambda@Edge: write a Lambda function that rewrites the request URI based on the Accept header, pointing to pre-generated AVIF/WebP variants on S3.

Fastly: use VCL to inspect req.http.Accept and modify req.url or set a cache variation key.

CDN-level negotiation is the lowest-effort approach for existing sites. The origin serves JPEG, the CDN converts or selects the right variant, and the client gets AVIF or WebP automatically.


Build Pipeline Integration

For static sites and build-step workflows, generate all format variants at build time:

Using the Morphix API in a build script:

#!/bin/bash
for file in src/images/*.{jpg,jpeg,png}; do
  [ -f "$file" ] || continue
  name=$(basename "${file%.*}")
  
  for format in webp avif; do
    curl -s -X POST https://morphix.tools/api/v1/convert \
      -H "Authorization: Bearer $MORPHIX_API_KEY" \
      -F "file=@$file" \
      -F "format=$format" \
      -F "quality=80" \
      --output "dist/images/$name.$format"
  done
  
  cp "$file" "dist/images/"
done

This produces three files per source image: original, WebP, and AVIF. Your HTML template then uses <picture> to reference all three.

Framework integration:

  • Next.js: the built-in <Image> component handles WebP/AVIF negotiation automatically via its image optimization API.
  • Astro: the <Image> component generates optimized formats at build time.
  • Gatsby: gatsby-plugin-image with Sharp generates WebP variants automatically.
  • Static HTML: use a build script as shown above, or a task runner like Make.

Testing and Validation

After implementation, validate that the right format is being served:

Browser DevTools. Open the Network tab, load the page, and check the Content-Type header of each image response. It should show image/avif, image/webp, or image/jpeg depending on your browser and configuration.

Test with different browsers. If you use content negotiation, test with a browser that does not support AVIF (or temporarily disable AVIF support in DevTools) to verify that the WebP fallback works.

Check for Vary: Accept. If you use server-side content negotiation, verify that the Vary: Accept header is present. Without it, CDN caches may serve AVIF to a browser that only supports JPEG.

Lighthouse. Run Lighthouse and check the "Serve images in next-gen formats" audit. It should pass if you are serving WebP or AVIF.

Visual comparison. Open the same page with and without format conversion and compare visual quality. At quality 80, the difference should be invisible.


Convert Your Images

Morphix converts JPG and PNG images to WebP and AVIF directly in your browser, or at scale via the API. No registration required for the free plan.

Convert to WebP | Convert to AVIF | API Documentation

Ready to optimize your images?

Start for free