HTTP 304 Not Modified
Updated Free mock REST & WebSocket APIs
HTTP 304 Not Modified — The cached copy the client already has is still valid, so no body is sent.
- Class
- 3xx Redirection
- Defined in
- RFC 9110 §15.4.5
- Official
- Yes (IANA registry)
- Cacheable by default
- No (unless Cache-Control/Expires say so)
Try it: get a real 304 response
Our test endpoint answers every request with 304 Not Modified.
curl -i https://mockfox.dev/api/status/304Use it in tests, health-check monitors or to see how your client handles a 304. Add ?delay=2000 to slow it down.
What does HTTP 304 mean?
304 Not Modified is the server's answer to a conditional request when the client's cached copy is still current. It has no body: the client reuses what it already has, saving bandwidth and time.
Browsers make these requests automatically. When a cached response has an ETag, they send If-None-Match: <etag>; when it has Last-Modified, they send If-Modified-Since: <date>. If nothing changed, the server replies 304 with refreshed caching headers.
When is 304 used?
- Revalidating cached HTML, CSS, JS or images after their max-age expired.
- API clients polling a resource with
If-None-Matchto detect changes cheaply. - CDNs revalidating their cached copy with the origin.
Why you're seeing 304
- Normal caching — it's a performance win, not an error.
- If you see stale content after a deploy, the server may be generating identical ETags/Last-Modified for changed files, or the HTML references un-fingerprinted asset URLs.
What to do about 304
If you're visiting the site
- Nothing to fix. If a page looks outdated, do a hard refresh (Ctrl+Shift+R / Cmd+Shift+R), which bypasses the cache.
If you run the site or API (developers)
- Send a strong
ETagor accurateLast-Modifiedon cacheable responses — Express and nginx do this for static files by default. - Fingerprint static assets (
app.3f9a1c.js) and cache them withCache-Control: public, max-age=31536000, immutableso they never need revalidation. - A 304 must not include a body, and should repeat headers like
ETag,Cache-ControlandVarythat would be sent with the 200. - In fetch(), a 304 from the browser cache surfaces as a 200; you'll only see 304 in DevTools or when you send conditional headers yourself.
Sending 304: server examples
nginx
location /static/ {
etag on; # default
expires 1y;
add_header Cache-Control "public, immutable";
}Express
// res.send() computes a weak ETag and answers 304 automatically
// when the request's If-None-Match matches (req.fresh === true).
app.get("/api/config", (req, res) => {
res.set("Cache-Control", "no-cache"); // always revalidate
res.json(config);
});Next.js
// app/api/example/route.ts (Next.js App Router)
export async function GET(req: Request) {
const { data, etag } = await getConfig();
if (req.headers.get("if-none-match") === etag) {
return new Response(null, { status: 304, headers: { ETag: etag } });
}
return Response.json(data, { headers: { ETag: etag, "Cache-Control": "no-cache" } });
}Example 304 response
HTTP/1.1 304 Not Modified ETag: "33a64df551425fcc55e4d42a148795d9" Cache-Control: max-age=3600 Date: Sun, 18 Oct 2026 10:00:00 GMT
Related status codes
- 200 OKThe request succeeded and the response contains the result.
- 412 Precondition FailedA condition in the request headers (If-Match, If-Unmodified-Since…) evaluated to false.
- 206 Partial ContentThe server is returning only the byte range the client asked for in its Range header.
Related tools
Frequently asked questions
Is 304 Not Modified an error?
No. It's a successful revalidation that tells the client to use its cached copy.
Does 304 affect SEO?
It helps crawl efficiency: Googlebot sends conditional requests and a 304 tells it nothing changed. Just make sure changed pages actually return 200 with the new content.
What's the difference between 304 and 200 (from cache)?
"200 (from disk/memory cache)" in DevTools means the browser didn't contact the server at all because the cached copy was still fresh. 304 means it asked the server, which confirmed the copy is still valid.