HTTP

Comparison

304 Not Modified vs 200 OK

Understand when servers return 304 Not Modified instead of 200 OK. Learn how conditional requests, ETags, and Last-Modified headers enable efficient HTTP caching.

Bottom line: Use 200 when the server needs to send the resource body. Use 304 when the client already has a cached copy and only needs confirmation that it is still current.

304 Not Modified
vs
200 OK

The Fast Mental Model

If you are looking at DevTools and wondering why the browser got “no real response body,” the answer is usually this:

304 Not Modified is not a failure and it is not an empty 200. It is a successful cache revalidation response.

The Core Difference

200 OK is the baseline response. The server sends headers and a full body.

304 Not Modified only appears when the client asks a conditional caching question, such as “has this changed since the version with this ETag?” If the answer is no, the server skips the body and the client reuses its cached content.

Why You See 304 In Real Life

You usually encounter 304 in one of these situations:

304 only happens when the client sends a conditional request:

# First request — client has nothing cached
GET /styles.css HTTP/1.1

# Server responds with full content + cache validators
HTTP/1.1 200 OK
ETag: "abc123"
Cache-Control: max-age=3600
[full CSS body]

# Later — cache has expired, client revalidates
GET /styles.css HTTP/1.1
If-None-Match: "abc123"

# Resource unchanged — server sends 304, no body
HTTP/1.1 304 Not Modified
ETag: "abc123"
Cache-Control: max-age=3600

Comparison Table

200 OK304 Not Modified
Response bodyYes (full resource)No
Triggered byAny GET/HEADConditional GET (If-None-Match / If-Modified-Since)
Client actionStore in cacheUse existing cached copy
Bandwidth usedFull resource sizeHeaders only (~200–500 bytes)
Requires prior requestNoYes (needs cached ETag or Last-Modified)

ETag vs Last-Modified

Servers can use two mechanisms to enable conditional requests:

ETag — a content fingerprint, usually a hash of the response body:

ETag: "d41d8cd98f00b204e9800998ecf8427e"

Client sends back as If-None-Match: "d41d8cd98f00b204e9800998ecf8427e".

Last-Modified — a timestamp of when the resource last changed:

Last-Modified: Tue, 18 Feb 2026 00:00:00 GMT

Client sends back as If-Modified-Since: Tue, 18 Feb 2026 00:00:00 GMT.

ETags are more reliable. A file can be regenerated with identical content (same ETag, different timestamp) or have its mtime updated without content changes. Use ETags when your server can compute them cheaply.

Cache-Control Interaction

304 only comes into play after a cache entry expires. The flow is:

  1. Fresh cache (max-age not exceeded) — browser uses cached copy directly, no request sent
  2. Stale cache (max-age exceeded) — browser sends conditional request → server returns 304 or 200
  3. No cache validators (no ETag, no Last-Modified) — browser must fetch full 200

Cache-Control: no-cache forces revalidation on every request (step 2 always), but still allows 304 if the server supports it. Cache-Control: no-store disables caching entirely — always 200.

Common Mistakes

Treating 304 like a missing response — it is not missing anything. The response is telling the client that the cached body is still authoritative.

Not sending ETag or Last-Modified — without validators, the browser cannot revalidate efficiently and must fall back to full 200 responses.

Confusing 304 with 204304 means “reuse your cached copy.” 204 No Content means “the request succeeded, and there is no body to send for this operation.”

Expecting 304 for POST — conditional cache revalidation is for GET and HEAD, not for write-oriented methods.