Quick answer
HTTP 304 Not Modified means your cached copy of the resource is still valid, so the server returns no body. It is sent in response to a conditional request.
What HTTP 304 means
304 Not Modified is part of HTTP caching. When a client sends a conditional request with If-None-Match (an ETag) or If-Modified-Since, and the resource has not changed, the server responds 304 with no body. The client then uses its cached copy, saving bandwidth.
Common causes
- A conditional
GETwithIf-None-Matchwhere the ETag still matches - A request with
If-Modified-Sincewhere the resource is unchanged - Browser or CDN cache revalidation
Response body
A 304 has no body — the client reuses its cached copy. Do not parse a response body on a 304.
Raw HTTP response
HTTP/1.1 304 Not Modified
ETag: "a1b2c3d4"
Cache-Control: max-age=3600
How to handle HTTP 304
- ✓ Serve the resource from your local cache
- ✓ Check the
ETag/Last-Modifiedvalues are being sent - ✓ Do not parse a response body — there is none
- ✓ Confirm your client sends
If-None-MatchorIf-Modified-Since
How conditional requests produce a 304
A 304 is the second half of a two-step caching conversation. First, the server hands the client a validator on the original 200 response. Later, the client sends that validator back, and the server decides whether anything changed:
# 1) First response includes a validator
HTTP/1.1 200 OK
ETag: "a1b2c3d4"
# 2) Client re-requests with the validator
GET /api/data HTTP/1.1
If-None-Match: "a1b2c3d4"
# 3) Unchanged → server saves bandwidth
HTTP/1.1 304 Not Modified
ETag vs Last-Modified
There are two validator mechanisms, and they pair up:
| Server sends | Client echoes | Based on |
|---|---|---|
ETag: "a1b2c3d4" | If-None-Match | A content hash/version — precise to the byte |
Last-Modified: <date> | If-Modified-Since | A timestamp — 1-second resolution |
Prefer ETag when you can compute one cheaply — it catches changes that share the same timestamp and avoids the 1-second granularity limit of Last-Modified.
Handling 304 in client code
Browsers handle 304 transparently — your fetch sees a 200 with the cached body, not a 304, because the cache layer resolves it. You typically only observe a raw 304 when you manage caching yourself (a CDN, an HTTP library with manual revalidation, or a server-to-server client storing ETags). In that case: on 304, reuse the stored body; on 200, replace your cache and store the new ETag.
Frequently Asked Questions
Why does a 304 have no body?
Because the client already has a valid cached copy. The whole point of 304 is to save bandwidth by telling the client "nothing changed, use what you have" instead of resending the resource.
What triggers a 304 response?
A conditional request: the client sends If-None-Match with an ETag, or If-Modified-Since with a date. If the resource has not changed, the server replies 304 instead of 200 with the full body.
Is 304 an error?
No. 304 is a normal, successful part of HTTP caching. It means the cached version is still good.
What is the difference between ETag and Last-Modified?
An ETag is a content version identifier (often a hash), echoed back via If-None-Match — precise to the byte. Last-Modified is a timestamp, echoed via If-Modified-Since — limited to 1-second resolution. ETag is more accurate; use it when you can generate one cheaply.
Why don't I ever see a 304 in my fetch code?
Because the browser's HTTP cache resolves it for you — your fetch receives a 200 with the cached body. You only observe raw 304s when you manage caching manually, such as in a CDN, a server-to-server client, or an HTTP library configured for manual revalidation.
Working with a JSON API response?
Format and inspect any response in your browser — nothing is uploaded.