How Cache-Control Immutable Stops Conditional Requests
The immutable directive in the HTTP
Cache-Control response header is a performance optimization
that instructs browsers never to revalidate a cached resource while it
is still fresh. This article explains the mechanism behind redundant
conditional requests, how the immutable directive
neutralizes them during user-initiated page refreshes, and best
practices for implementing it effectively in modern web
architectures.
The Problem: Redundant Conditional Requests
When a web browser stores a resource with standard caching headers
(such as Cache-Control: max-age=31536000), the asset is
considered fresh for up to one year. During normal navigation, the
browser serves this asset directly from its local disk or memory cache
without contacting the origin server.
However, when a user explicitly refreshes a page (via the reload
button or F5), standard browser behavior overrides the
freshness lifetime. To ensure the user sees the latest version, the
browser issues a conditional request containing validation headers:
If-None-Match: Sends the resource'sETagvalidator.If-Modified-Since: Sends the timestamp of the cached file.
The server inspects these headers, confirms the file has not changed,
and returns an HTTP 304 Not Modified status code without a
response body.
While an HTTP 304 saves bandwidth by not re-transmitting the asset, it still incurs significant overhead. Each conditional request requires a complete network round-trip time (RTT), TLS negotiation latency, and server processing time. On a webpage with dozens of static subresources, dozens of 304 requests can severely delay page rendering.
How the
immutable Directive Fixes This
The immutable directive signals to the client that the
body of the response will never change over time. When applied alongside
a max-age value, it modifies the browser's refresh
behavior:
Cache-Control: public, max-age=31536000, immutable
With immutable present, the browser bypasses validation
completely:
- Freshness Check: The browser determines if the
asset is within its
max-agewindow. - Reload Handling: If the user reloads the page, the
browser respects the
immutabledirective and retrieves the resource directly from local cache. - Request Elimination: The browser sends zero HTTP
requests to the server for that resource, preventing both
200 OKtransfers and304 Not Modifiedround-trips.
Only a "hard refresh" (such as Ctrl + F5 or
Cmd + Shift + R) will force the browser to ignore
immutable and contact the server.
Implementation Requirements and Best Practices
Because an immutable file cannot be altered once cached, the directive must be used strictly with cache-busting strategies.
1. Use Fingerprinted URLs
Only apply immutable to assets that contain a
cryptographic hash or unique version identifier in their filename:
- Correct:
/assets/app.d41d8cd98f00.js - Correct:
/styles/theme.v2.4.1.css - Incorrect:
/styles/main.css - Incorrect:
/index.html
If the contents of a fingerprinted file change, the build process generates a new URL. The HTML document references the new URL, prompting the browser to download the new asset while leaving the old cached version to expire naturally.
2. Pair with Long Expiration Windows
The immutable directive only operates while a resource
is fresh. It must be paired with an explicit max-age
(typically one year, or 31536000 seconds):
Cache-Control: max-age=31536000, immutable
If the max-age expires, the browser will once again
issue conditional requests to revalidate the asset.