1. Library
  2. Computer Networks
  3. Http and the Web
  4. Status Codes

Updated 9 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

GET /style.css HTTP/1.1
Host: www.example.com

HTTP/1.1 200 OK
Content-Type: text/css
Last-Modified: Wed, 21 Oct 2024 07:00:00 GMT
ETag: "686897696a7c876b7e"
Cache-Control: max-age=0, must-revalidate

body { color: blue; }

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:

GET /style.css HTTP/1.1
Host: www.example.com
If-Modified-Since: Wed, 21 Oct 2024 07:00:00 GMT
If-None-Match: "686897696a7c876b7e"

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:

HTTP/1.1 304 Not Modified
ETag: "686897696a7c876b7e"
Cache-Control: max-age=3600

No body. No content. Just confirmation. The browser uses its cached copy.

If the resource has changed:

HTTP/1.1 200 OK
Content-Type: text/css
Last-Modified: Wed, 21 Oct 2024 08:30:00 GMT
ETag: "7789a8b7c93d987e8"
Cache-Control: max-age=3600

body { color: red; }

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:

If-Modified-Since: Wed, 21 Oct 2024 07:00:00 GMT

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:

If-None-Match: "686897696a7c876b7e"

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):

const crypto = require('crypto');
const hash = crypto.createHash('md5').update(content).digest('hex');
const etag = `"${hash}"`;

File metadata (faster, less precise):

const stats = fs.statSync(filePath);
const etag = `"${stats.size}-${stats.mtime.getTime()}"`;

Version number (for versioned resources):

const etag = `"${resourceId}-${version}"`;

Strong vs. Weak ETags

A strong ETag means byte-for-byte identical:

ETag: "686897696a7c876b7e"

A weak ETag means semantically equivalent (the W/ prefix):

ETag: W/"686897696a7c876b7e"

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:

HTTP/1.1 304 Not Modified
Date: Wed, 21 Oct 2024 07:28:00 GMT
ETag: "686897696a7c876b7e"
Cache-Control: max-age=3600

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

app.get('/style.css', function(request, response) {
    const resource = loadResource('style.css');
    const currentETag = calculateETag(resource);
    const currentModified = resource.lastModified;

    const clientETag = request.headers['if-none-match'];
    const clientModifiedSince = request.headers['if-modified-since'];

    // Check if resource hasn't changed
    if (clientETag === currentETag ||
        (clientModifiedSince && new Date(clientModifiedSince) >= currentModified)) {
        response.status(304);
        response.setHeader('ETag', currentETag);
        response.setHeader('Cache-Control', 'max-age=3600');
        response.end(); // No body
    } else {
        response.status(200);
        response.setHeader('Content-Type', 'text/css');
        response.setHeader('ETag', currentETag);
        response.setHeader('Last-Modified', currentModified.toUTCString());
        response.setHeader('Cache-Control', 'max-age=3600');
        response.send(resource.content);
    }
});

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

  1. Open DevTools (F12)
  2. Go to the Network tab
  3. Load a page
  4. Reload it
  5. Look for 304 status codes
  6. 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?

😔
🤨
😃