skip to content

An image CDN serves AVIF, WebP or JPEG from one URL depending on the request's Accept header. What must the response do so that caches in front of it stay correct, and what does that cost?

level: seniorimportance: should knowfreq 36%

answer

  1. body depends on the request, not just the URL
  2. tell shared caches what varied
  3. raw headers differ cosmetically
  4. bucket into a few format classes

basics

~20 s

A response whose encoding depends on the request must tell shared caches so, with Vary: Accept or an equivalent normalized cache key. Without it a cache can hand AVIF bytes to a client that cannot decode them; with a naive Vary it fragments into many near-identical copies.

solid answer

~60 s

When one URL can return several encodings, the response body is no longer a function of the URL alone — it depends on the request's `Accept` header. Any shared cache between the CDN and the user must be told that, or it will store whatever the first client received and replay it to everyone, which is how a browser without AVIF support ends up with an image it cannot render. The standard signal is `Vary: Accept`. The cost is fragmentation: real `Accept` strings differ across browsers and versions, so a cache keying on the raw header stores many byte-identical copies and the hit rate falls. The better pattern, and what mature image CDNs do internally, is to **normalize** the header into a small number of buckets — supports AVIF, supports WebP, neither — and key on the bucket. Some teams sidestep negotiation entirely by putting the format in the URL and letting the markup choose, which trades a slightly larger cache-friendly surface for full control over what each client gets.

go deeper

for a junior

Know that one URL can return different image formats depending on what the browser says it accepts, and that caches need to be told when that happens.

for a middle

Explain the role of Vary: Accept and describe the concrete failure when it is missing — a shared cache replaying one encoding to a client that never asked for it.

for a senior

Show the tradeoff both ways: name fragmentation as the cost of a naive Vary, propose normalizing the header into a few buckets, and describe how you would debug a browser-specific broken image.

for a principal

Decide where format policy lives. Own the argument between edge negotiation and format-in-URL, including what each costs in cache keys, debuggability, and your ability to roll back a format decision for one asset.

## One URL, several possible bodies Most image CDNs offer an automatic format mode: the same URL returns AVIF to a client that accepts AVIF, WebP to one that accepts WebP, and JPEG otherwise. It is convenient because the page holds a single URL and format policy lives at the edge. The convenience has a consequence that is easy to miss. HTTP caching is built on the assumption that a URL identifies a representation. Once the bytes depend on the *request*, every cache in the path needs to know which part of the request mattered. ## What the browser actually sends Browsers advertise image support in the `Accept` header on image requests. Chrome, for instance, sends an image `Accept` that lists `image/avif` and `image/webp` ahead of the wildcard entries; Safari has advertised AVIF since Safari 16. The CDN reads that list and picks the best encoding it can produce. The important detail for caching is that these header strings are **not uniform**. They differ between engines, they change between versions, and intermediaries sometimes rewrite them. Treating the raw string as part of the cache key means treating those cosmetic differences as meaningful. ## The correctness problem If the response does not declare its dependence on `Accept`, a shared cache — a CDN layer you do not control, a corporate proxy — is free to store one response and serve it to everyone. The failure looks like this: 1. A Chrome user requests the hero; the CDN returns AVIF. 2. A proxy caches that response under the URL. 3. An older client that never advertised AVIF requests the same URL and receives the AVIF bytes. 4. The image does not render, and it does not render *only for some users, in some networks* — the worst kind of bug to reproduce. Declaring the dependency fixes this: ``` Content-Type: image/avif Vary: Accept Cache-Control: public, max-age=31536000, immutable ``` Now a compliant cache stores per distinct `Accept` value and will not cross-serve. ## The cost: fragmentation `Vary: Accept` is correct but blunt. Because the header varies cosmetically, a cache honouring it literally may hold five copies of one derivative that differ only in which client asked. Every extra copy is a separate first-request miss to pay and separate storage to fund, and the hit rate falls exactly where you least want it. The mitigation is **normalization**. Instead of keying on the raw header, the edge maps it to a small enumeration — say `avif`, `webp`, `legacy` — and keys the derivative on that bucket. Three buckets means at most three copies rather than a long tail. Purpose-built image CDNs generally do this for you; a general-purpose CDN in front of your own transform service usually needs it configured explicitly. Either way, the response sent onward should still carry `Vary: Accept` so caches you do not control behave. ## The alternative: put the format in the URL Negotiation is not the only option. You can serve each encoding from its own URL and let the page decide which to request. That makes every URL a stable, unambiguous representation — no `Vary`, no bucketing, no cross-serving risk — at the cost of the page having to express the choice and of splitting traffic across more keys. It also gives you an escape hatch when a format turns out to be wrong for a particular asset: you change one URL rather than a global policy. Which side you land on usually comes down to whether format policy belongs to the edge or to the template; both are defensible. ## Debugging it When someone reports a broken image on one browser only, check three things in order. First, the `Content-Type` actually returned for that user's request — not the one you get. Second, whether the response carries a `Vary` header at all. Third, whether an intermediary is involved, by comparing a request made with a restricted `Accept` against one made with a permissive one; if both come back with the same encoding, something upstream has collapsed the negotiation. One last trap: browser caches also honour `Vary`. A client whose `Accept` changes after a browser upgrade will simply miss its previously cached entries and refetch. That is correct behaviour, but it explains a hit-rate dip after a major browser release that has nothing to do with your deploy.

  • If the CDN normalizes Accept into buckets internally, does it still need to send Vary: Accept downstream?
    Yes. Normalization only fixes the key inside that CDN; caches further along — proxies, other CDN layers, the browser itself — still see one URL returning different bodies. Sending `Vary: Accept` keeps them correct. The two are complementary: normalization protects your hit rate, the header protects everyone else's correctness.
  • What is the argument for putting the format in the URL instead of negotiating on Accept?
    Stability and control. Each URL then names exactly one representation, so there is no `Vary`, no risk of a cache cross-serving an undecodable body, and no dependence on header fidelity through intermediaries. You pay for it by splitting traffic across more cache keys and by moving the format decision into the page rather than the edge.
  • Why might image cache hit rate drop right after a major browser release, with no change on your side?
    Because browser upgrades sometimes change the `Accept` header they send. Under `Vary: Accept`, a different header means a different cache entry, so a large population suddenly misses entries it previously hit and refetches. It resolves as the new entries warm, and normalizing the header into a few buckets prevents most of it.

saying these in an interview costs you the question

  • Says Vary: Accept is unnecessary because the CDN handles it
  • Thinks the Content-Type header alone prevents cross-serving
  • Assumes all browsers send an identical Accept header
  • Believes automatic format selection has no caching cost

context