Skip to main content

Cache-Control Directives

· 14 min read
Phan Thanh Sang
Front End Engineer @ Sea Limited

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 (or Pragma: 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

ActionMain DocumentSubresourcesRequest Header
Normal NavigationUse cache if freshUse cache if freshNormal caching rules apply
Soft Reload (Ctrl+R)Revalidate with max-age=0Use cache if freshCache-Control: max-age=0 (main doc only)
Hard Reload (Ctrl+Shift+R)Bypass cache, fetch freshBypass cache, fetch freshCache-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

  1. Use immutable for versioned assets: If your build process adds content hashes to filenames, use immutable to prevent unnecessary revalidation.

  2. Set appropriate max-age values: Balance freshness requirements with server load. Longer is better for static assets, shorter for dynamic content.

  3. Use private for user-specific data: Prevent CDNs and proxies from caching personalized content.

  4. Use no-store sparingly: It prevents all caching benefits. Consider no-cache if you just need validation.

  5. Combine with ETags: Use ETags with no-cache or must-revalidate to enable efficient revalidation.

  6. 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 or private
  • How often does it change? → Set appropriate max-age
  • Is the filename versioned? → Add immutable
  • Must it always be fresh? → Use no-cache or must-revalidate
  • Is it sensitive? → Use no-store

For more detailed information, refer to the MDN Cache-Control documentation.

References