Cache-Control Directives
Caching is one of the most powerful tools for optimizing web performance. The Cache-Control
HTTP header provides fine-grained control over how browsers and intermediary caches store and reuse HTTP responses. In this article, we'll explore the most important Cache-Control directives and understand when and how to use them effectively.
What is Cache-Control?
The Cache-Control
HTTP header field holds directives (instructions) that control caching in both requests and responses. These directives define who can cache the response, under what conditions, and for how long.
Types of Caches:
- Browser Cache: A private cache stored locally in the user's browser
- CDN (Content Delivery Network) Cache: Shared caches distributed geographically to serve content closer to users
- Proxy Cache: Intermediate caches between the browser and origin server (e.g., corporate proxies, ISP caches)
Different Cache-Control directives apply to different types of caches, which we'll explore in detail below.
How Browser Caching Works
Before diving into specific directives, let's understand the basic flow of browser caching:
Cache-Control Directives
0. No Cache-Control Header
When a response has no Cache-Control
header, the browser uses heuristic caching based on other headers like Last-Modified
or Expires
, or may not cache at all.
Response:
(No Cache-Control header)
Use case: Avoid this situation. Always set explicit Cache-Control
headers for predictable caching behavior.
1. max-age=<seconds>
The max-age
directive specifies the maximum amount of time (in seconds) a resource is considered fresh. Once this time expires, the cache must revalidate the resource with the origin server.
Applies to: Request and Response
Cache-Control: max-age=3600
This tells the browser that the resource is fresh for 1 hour (3600 seconds).
Use case: Static assets like CSS, JavaScript, or images that don't change frequently.
2. no-cache
Despite its name, no-cache
doesn't mean "don't cache." It means the cache (browser cache, CDN, or proxy) must revalidate the response with the origin server before serving it, even if it appears fresh.
Applies to: Request and Response
Cache-Control: no-cache
Use case: Resources that need to be validated before each use, like API responses or frequently updated content.
3. no-store
The no-store
directive prohibits any caching whatsoever. The response should not be stored in any cache (browser cache, CDN, or proxy), and every request must fetch from the origin server.
Applies to: Request and Response
Cache-Control: no-store
Use case: Sensitive data like banking information, personal details, or any content that shouldn't be stored locally.
4. public
The public
directive indicates that the response may be cached by any cache, including CDNs and proxy servers, even if it's associated with an authenticated user.
Applies to: Response only
Cache-Control: public, max-age=86400
Use case: Static assets, public images, CSS, JavaScript files that are the same for all users.
5. private
The private
directive indicates that the response is intended for a single user and must not be stored by shared caches like CDNs or proxies. Only the browser cache may store it.
Applies to: Response only
Cache-Control: private, max-age=3600
Use case: User-specific content like personalized dashboards, account information, or responses containing user data.
6. must-revalidate
The must-revalidate
directive tells the cache (browser cache, CDN, or proxy) that once a resource becomes stale, it must not be served without successful revalidation with the origin server.
Applies to: Response only
Cache-Control: max-age=3600, must-revalidate
Use case: Critical resources where serving stale content could cause problems, like transaction data or inventory information.
7. immutable
The immutable
directive indicates that the response body will not change over time. The browser should not make a conditional request to revalidate the resource, even when the user reloads the page.
Applies to: Response only
Cache-Control: public, max-age=31536000, immutable
Use case: Assets with content-based hashing in their filenames (e.g., app.a3d8f9.js
). Since the filename changes when content changes, the cached version is always correct.
Combining Directives
Cache-Control directives can be combined for fine-grained control:
Cache-Control: public, max-age=31536000, immutable
This is perfect for versioned static assets.
Cache-Control: private, no-cache, must-revalidate
This ensures user-specific content is always validated before serving.
Cache-Control: public, max-age=3600, must-revalidate
This caches content for 1 hour but requires revalidation when stale.
Cache Hit vs Cache Miss: Complete Flow
Here's a comprehensive diagram showing the complete flow of cache hits and misses in the browser cache:
Browser Reload Behavior
Understanding how browsers handle page reloads is crucial for testing and debugging caching behavior. Browsers have different reload modes that affect how they interact with the cache.
Soft Reload (Normal Reload)
A soft reload happens when you press the refresh button or use Ctrl+R
(Windows/Linux) or Cmd+R
(Mac). In this mode, the browser:
- Respects
Cache-Control
headers - Will serve cached resources if they're still fresh
- For the main HTML document, the browser sends a conditional request with
Cache-Control: max-age=0
to check for updates
Reference: MDN: Reload
Hard Reload (Force Reload)
A hard reload happens when you press Ctrl+Shift+R
(Windows/Linux) or Cmd+Shift+R
(Mac). In this mode, the browser:
- Ignores all cached content
- Sends
Cache-Control: no-cache
(orPragma: no-cache
) in the request headers - Forces revalidation of all resources
- Bypasses the browser cache entirely for the reload
Reference: MDN: Cache-Control no-cache
Comparison Table
Action | Main Document | Subresources | Request Header |
---|---|---|---|
Normal Navigation | Use cache if fresh | Use cache if fresh | Normal caching rules apply |
Soft Reload (Ctrl+R ) | Revalidate with max-age=0 | Use cache if fresh | Cache-Control: max-age=0 (main doc only) |
Hard Reload (Ctrl+Shift+R ) | Bypass cache, fetch fresh | Bypass cache, fetch fresh | Cache-Control: no-cache (all resources) |
Clearing the Cache
To completely clear the browser cache:
- Chrome: DevTools → Network tab → Disable cache (while DevTools is open)
- Chrome: Settings → Privacy and security → Clear browsing data → Cached images and files
- Firefox: DevTools → Network tab → Gear icon → Disable HTTP Cache
- Safari: Develop → Empty Caches
Reference: MDN: HTTP Caching
Practical Examples
Example 1: Static Assets with Versioning
GET /assets/app.a3d8f9.js
Response:
Cache-Control: public, max-age=31536000, immutable
Since the filename includes a hash, it's safe to cache forever with the immutable
directive.
Example 2: API Response with User Data
GET /api/user/profile
Response:
Cache-Control: private, no-cache
The response contains user-specific data that should only be cached in the browser and must be revalidated on each request.
Example 3: Dynamic Content with Short TTL
GET /news/latest
Response:
Cache-Control: public, max-age=300, must-revalidate
News content can be cached for 5 minutes, but must be revalidated when stale to ensure users see recent updates.
Example 4: Sensitive Banking Data
GET /api/account/transactions
Response:
Cache-Control: no-store, private
Banking transactions should never be cached anywhere.
Best Practices
-
Use
immutable
for versioned assets: If your build process adds content hashes to filenames, useimmutable
to prevent unnecessary revalidation. -
Set appropriate
max-age
values: Balance freshness requirements with server load. Longer is better for static assets, shorter for dynamic content. -
Use
private
for user-specific data: Prevent CDNs and proxies from caching personalized content. -
Use
no-store
sparingly: It prevents all caching benefits. Considerno-cache
if you just need validation. -
Combine with ETags: Use ETags with
no-cache
ormust-revalidate
to enable efficient revalidation. -
Consider
stale-while-revalidate
: This modern directive allows serving stale content while updating in the background.
Conclusion
Understanding Cache-Control directives is crucial for building performant web applications. By choosing the right combination of directives, you can significantly reduce server load, decrease latency, and improve user experience while maintaining data freshness and security.
The key is to understand your content's characteristics:
- Is it public or private? → Use
public
orprivate
- How often does it change? → Set appropriate
max-age
- Is the filename versioned? → Add
immutable
- Must it always be fresh? → Use
no-cache
ormust-revalidate
- Is it sensitive? → Use
no-store
For more detailed information, refer to the MDN Cache-Control documentation.