Two approaches to safe format delivery
Browser support for the newest image formats — AVIF, WebP, JPEG XL — is excellent in
modern browsers but is never 100% across the full range of devices in the wild. You
cannot just swap your .jpg references for .avif and call it
done: a browser that does not understand AVIF will render a broken image. You need a
strategy for serving the best available format to each visitor while guaranteeing a
working fallback.
There are two fundamentally different strategies, and choosing between them is a practical architecture decision:
-
Client-side selection with
<picture>— you ship multiple versions of each image (AVIF, WebP, JPEG) and let the browser choose. The server just serves whatever file is requested. No server configuration needed. - Content negotiation — the browser sends the request, the server inspects which formats the browser accepts, and returns the best format at a single URL. The server (or CDN) does the work.
Side-by-side comparison
| Property | <picture type> (client-side) |
Content negotiation (server-side) |
|---|---|---|
| Where the decision happens | In the browser, before any network request | On the server / CDN, after the request arrives |
| Number of image URLs | One per format variant (multiple URLs in the HTML) | One URL; format is transparent to the page |
| Server configuration required | None — any static file host works | Yes — server must read Accept and vary the response |
| HTML complexity | Grows with the number of format variants | Stays simple — just a plain <img src> |
| Cache behaviour | Each URL caches independently | Requires Vary: Accept so caches store one copy per format |
| Best fit for | Static sites, no back-end, small image sets | Dynamic sites, large image libraries, CDN pipelines |
Tutorial 07: The picture element & art direction
covered the <picture type> approach in depth — including why combining
format, crop, and colour-scheme variants can require a large number of
<source> elements. This tutorial picks up from there and looks at
the server-side alternative.
Content negotiation
Content negotiation is a standard HTTP mechanism in which the client advertises what
it can handle in a request header and the server responds with the best matching
representation. It is not specific to images — the same mechanism drives language
selection (Accept-Language) and compression (Accept-Encoding
for gzip/Brotli). Images use the Accept header.
What the browser sends
Every image request from a modern browser includes an Accept header
listing the MIME types it is willing to receive, ordered roughly by preference:
A browser that supports AVIF lists image/avif first. One that supports
WebP but not AVIF leads with image/webp. An older browser may only send
image/*, meaning "any image format." The server reads this header to
decide what to return.
What the server returns
If the server is configured to handle format negotiation, the response for that same URL looks like this for an AVIF-capable browser:
And for a browser that only supports WebP:
The URL is identical in both cases. The difference in the response is transparent to
the HTML — the <img src> just references
/images/hero.jpg and each browser silently receives the best format it
can handle.
Why Vary: Accept is essential
This is the part most often overlooked. HTTP caches — both the browser's own cache and
any intermediate CDN or proxy — key their stored responses on the request URL by
default. If the server returns AVIF for one request and WebP for the next, and there
is no Vary header, a cache might store the first response (AVIF) and
serve it to a browser that asked for WebP — which would render a broken image.
The Vary: Accept response header tells caches: "store a separate cached
copy for each distinct Accept value." The cache key becomes URL +
Accept header combination, so the right format is always served to the right
client. Without it, content negotiation is broken at scale.
The same idea applies to compression and language
Accept-Encoding: gzip, br is content negotiation for compression: the
browser advertises support for gzip and Brotli, and the server compresses the
response body accordingly. Accept-Language: en-GB, en;q=0.9 is
the same idea for internationalised content. The image negotiation described above
is the same pattern applied to image formats. Once you understand one flavour of
content negotiation you understand all of them.
Image CDNs
Setting up per-format serving on your own server is non-trivial: you need to pre-generate
each format variant, configure the web server or application to read Accept,
and add Vary: Accept correctly. For sites with hundreds or thousands of
images, a more practical solution is an image CDN.
Services like Cloudinary and Imgix are purpose-built for this. Instead of uploading multiple copies of an image, you upload one original and request variants on the fly via URL parameters. The CDN handles conversion and resizing at its edge servers — machines geographically close to each user — so the optimised image is served from a nearby node rather than a distant origin server. This matters for latency; it is the same "from nearby" principle described in 01: Why images matter.
URL-parameter APIs
Each image CDN has its own parameter syntax, but the concepts are consistent. A Cloudinary URL might look like this:
The transformation parameters here are:
f_auto— automatically pick the best format for the requesting browser (AVIF, WebP, or JPEG as appropriate)q_auto— automatically pick a compression quality levelw_800— resize to 800 px wide
An Imgix URL follows similar logic:
The CDN performs content negotiation on your behalf — it reads the browser's
Accept header and returns image/avif, image/webp,
or image/jpeg accordingly. You write a single URL; the CDN handles the
rest, sets Vary: Accept, and stores each variant in its edge cache.
Using CDN URLs in responsive markup
CDN URLs compose cleanly with srcset because each size is just a
different URL parameter. You get responsive images and automatic format selection
without managing any files yourself:
Each entry in srcset is the same image at a different width. The CDN
applies f_auto to every request, so an AVIF-capable browser on a fast
connection downloads a small AVIF, while an older device gets a JPEG — all from the
same short list of URLs.
The dependency trade-off
Image CDNs are a third-party dependency. If the CDN has an outage, your images go
down with it. There are also cost considerations: most services charge per image
transformation or per bandwidth consumed, so costs can grow with traffic. For a small
personal project or a simple marketing site, the <picture> approach
with locally-stored files is often the right call. For a publishing platform, an
e-commerce catalogue, or any site where images are core to the product, an image CDN
typically pays for itself in reduced file sizes, engineering time saved, and the
improved performance that comes from edge delivery.
TL;DR — which approach should you use?
There is no single right answer, but the decision is usually straightforward:
-
Static site or small image set: use
<picture>with<source type>. Pre-generate AVIF and WebP alongside your JPEG originals (ImageMagick, Squoosh, or your build tool can automate this), write the<picture>markup once, and you are done. No server configuration, no third-party accounts. -
Many images or a dynamic image pipeline: content negotiation at
the server or CDN layer removes the need to write per-format HTML. The HTML stays
clean (
<img src>), and the server handles the rest. Requires proper server setup and correctVary: Acceptheaders — see the server-side section and the HTTP section for the mechanics. - Image-heavy application or need for edge delivery: an image CDN (Cloudinary, Imgix, or a similar service) is the pragmatic choice. You get automatic format selection, on-the-fly resizing, and edge caching for one subscription cost. The URL-parameter API replaces both the server configuration and the need for multiple files.
The unifying principle: the browser will always receive the format it asked for —
the question is only who makes that decision and where. On the
client, it is the <picture> element. On the server, it is content
negotiation. At the edge, it is an image CDN. All three achieve the same goal.