304 Not Modified
304 Not Modified is the server's reply to a conditional request: the client's cached copy has not changed, so the server sends no body and the client uses what it already has. It is a success, not an error.
The client's cached copy is still current, so the server sends no body.
The answer to a conditional request carrying If-None-Match (an ETag) or If-Modified-Since. The client reuses its stored body and updates the stored headers from the 304. It is not an error, and it is not a redirect despite being in the 3xx range.
Explained
When a cached response goes stale, the browser revalidates instead of downloading again. It sends If-None-Match with the stored ETag, or If-Modified-Since with the stored Last-Modified date. If nothing changed, the server answers 304 with headers only; otherwise it sends a fresh 200.
The 304 should repeat headers that affect caching, such as ETag, Cache-Control and Expires, so the client can update its stored copy's freshness.
A 304 still costs a round trip. If assets are fingerprinted (app.3f9a1c.js), serve them with a long max-age and immutable so the browser does not revalidate at all.
Gotchas
- Load balancers that serve the same file from several servers can produce different ETags per server, so revalidation keeps failing and every request gets a full 200.
- Compression can alter ETags: nginx turns strong ETags into weak ones (
W/"...") when it gzips a response. - Client code that treats any status other than 200 as failure breaks when a cache layer passes a 304 through; fetch() in browsers handles revalidation transparently and shows 200.