Website Spec
Performance Recommended Updated

Conditional requests (ETag, Last-Modified, 304)

Send a validator — ETag or Last-Modified — on every cacheable response, and honour If-None-Match / If-Modified-Since so unchanged resources return an empty 304 instead of the full body.

What it is

A conditional request asks the server for a resource only if it has changed. The server attaches a validator to each response — an ETag (an opaque token identifying the body) or a Last-Modified date:

ETag: "87d3702ef9500aacbb5164d5fb9d82a4"
Last-Modified: Mon, 09 Jun 2026 09:00:00 GMT

On the next fetch the client echoes that validator back:

If-None-Match: "87d3702ef9500aacbb5164d5fb9d82a4"
If-Modified-Since: Mon, 09 Jun 2026 09:00:00 GMT

If the resource is unchanged, the server answers 304 Not Modified with empty body and the client reuses its cached copy. If it changed, the server returns the full 200. This is the revalidation step that Cache-Control defers to — see Cache-Control headers.

Why it matters

Cache-Control decides how long a cached copy stays fresh; validators decide what revalidation costs once it goes stale. Without a validator, every revalidation re-downloads the whole body even when nothing changed. With one, an unchanged 2 MB asset becomes a sub-200-byte 304. It is the difference between “is this still current?” costing a header exchange versus a full transfer.

It also matters for agents. A client polling this site’s Markdown mirrors can send If-None-Match and receive a 304 for content that has not changed, instead of re-parsing an identical page every cycle — cheap, polite re-fetching that pairs naturally with stable URLs. This site emits validators on every response; a conditional GET of any unchanged asset returns 304.

How to implement

  • Emit a validator on cacheable responses. Most servers and CDNs add ETag to static files automatically. Confirm it reaches the client and is not stripped by a proxy.
  • Prefer ETag over Last-Modified. Last-Modified has one-second granularity and breaks for content that changes sub-second or has no meaningful file date. ETag is exact. When both are present, If-None-Match takes precedence.
  • Use weak ETags (W/"…") for negotiated content. A weak validator means “semantically equivalent”, which is correct when the same URL is served gzip to one client and brotli to another. Pair with a correct Vary so caches do not cross the wires — see compression.
  • Keep the validator stable across identical bytes. It must not change on every request (e.g. derived from a timestamp), or revalidation never short-circuits.

Common mistakes

  • No validator at all — revalidation re-downloads the full body every time.
  • A strong ETag on content-negotiated responses, so a brotli client and a gzip client fight over one cache entry.
  • ETags that vary per request (random or time-based), defeating every conditional request.
  • Returning 200 with a body when the client’s If-None-Match already matches — wasted bandwidth.

Verification

  • curl -sI https://example.com/asset.js | grep -i etag — confirm a validator is present.
  • Re-request with it: curl -s -o /dev/null -w "%{http_code}" -H 'If-None-Match: "…"' https://example.com/asset.js — expect 304.
  • DevTools → Network → a revalidated resource shows status 304 and a tiny transfer size.

Related topics

Sources & further reading

Search
esc close navigate open