Updated 10 hours ago
When your browser asks a server for a resource, it often already has that resource cached. The question isn't "give me this file"—it's "has this file changed since I last got it?"
The 304 Not Modified status code is the server's answer: No, it hasn't. Use what you have.
This single response—containing no body, just headers—saves the entire download. A 2MB image becomes a 200-byte confirmation. Multiply that across every asset on every page load, and you understand why 304 is one of the most important performance optimizations in HTTP.
The Conditional Request Pattern
The 304 only makes sense as part of a conversation. Here's how it works:
First Visit: Get the Resource
The browser caches the CSS and stores two pieces of information: when it was last modified, and its ETag (a fingerprint of the content).
Return Visit: Ask If It Changed
When the cache needs validation, the browser makes a conditional request:
The browser is saying: "I have a version from this date with this fingerprint. Is it still good?"
The Server Decides
If the resource hasn't changed:
No body. No content. Just confirmation. The browser uses its cached copy.
If the resource has changed:
New content, new fingerprint, cache updated.
Two Ways to Validate: Time vs. Fingerprint
HTTP provides two validation mechanisms. Each has tradeoffs.
Last-Modified (Timestamp)
The client sends:
The server checks: Was this file modified after that date?
Good: Simple. Uses file system timestamps. Human-readable.
Limited: One-second granularity. If a file changes twice in the same second, or if the timestamp updates without the content changing, validation breaks.
ETag (Fingerprint)
The client sends:
The server checks: Does this fingerprint match the current content?
Good: Precise. Detects any content change instantly. Not dependent on clocks.
Requires: Computing or tracking the fingerprint.
Use Both
Clients can send both headers. If either indicates the resource hasn't changed, the server can return 304. ETags take precedence when both are present because they're more precise.
Best practice: Implement both. ETags for precision, Last-Modified for compatibility with older clients.
Generating ETags
ETags can be computed several ways:
Content hash (most precise):
File metadata (faster, less precise):
Version number (for versioned resources):
Strong vs. Weak ETags
A strong ETag means byte-for-byte identical:
A weak ETag means semantically equivalent (the W/ prefix):
Weak ETags allow minor differences—different compression, different whitespace—while still indicating the content is functionally the same.
The 304 Response: Headers Only
A 304 response must not include a body. It's just headers:
Include: Date, ETag, Cache-Control, Expires (if used)
Exclude: Content-Type, Content-Length (or set to 0), any body content
Including the ETag in the 304 response is important—it tells the client what to send in future If-None-Match headers.
Server Implementation
Load Balancing: The ETag Consistency Problem
In load-balanced environments, every server must generate identical ETags for the same content. If server A computes "abc123" and server B computes "xyz789" for the same file, validation fails randomly—the client sends an ETag that doesn't match, and gets a full download instead of a 304.
Solutions:
- Use content-based hashes (same content = same hash regardless of server)
- Share ETag computation across servers
- Use a CDN that handles ETags consistently
Watching 304s in Action
- Open DevTools (F12)
- Go to the Network tab
- Load a page
- Reload it
- Look for 304 status codes
- Notice the Size column shows tiny transfers or "from cache"
The browser handles all of this automatically. When you see a 304, you're watching HTTP do its job—confirming that nothing changed, saving a download, making the web faster.
Frequently Asked Questions About 304 Not Modified
Was this page helpful?