Browser Cache vs CDN Cache: How the Layers Interact
Web caching operates in layers, and the two most important layers are the browser cache and the CDN cache. Each serves a fundamentally different role in the delivery chain: the browser cache is a private store on the user's device that eliminates network requests entirely, while the CDN cache is a shared store on edge servers that reduces origin load and latency for all users in a region. Understanding how these layers interact, which Cache-Control directives target each layer, and how to debug issues across both is essential for building a performant caching strategy.
What Is Browser Cache and How Does It Work?
The browser cache is a local storage mechanism built into every web browser that saves copies of HTTP responses on the user's device. When the browser needs a resource it has previously fetched, it checks its local cache first before making any network request. Browser caching operates through several internal storage tiers, each with different speed and persistence characteristics.
Memory Cache
The fastest tier stores responses in the browser process's memory (RAM). Memory-cached resources are available instantly with zero disk I/O. However, memory cache is cleared when the tab or browser is closed. It primarily holds resources used during the current page session, such as images, scripts, and stylesheets that are referenced on the currently loaded page.
Disk Cache
The disk cache persists responses to the device's storage drive. It survives tab closes and browser restarts. Disk-cached resources require disk I/O to retrieve, making them slower than memory cache but still orders of magnitude faster than a network fetch. Most browsers maintain a disk cache of several hundred megabytes to a few gigabytes, with least-recently-used eviction when the limit is reached.
Service Worker Cache
Service workers are programmable proxies that intercept network requests from the browser before they reach either the HTTP cache or the network. A service worker can store responses in the Cache API, a separate storage mechanism that the developer controls programmatically. Service workers enable offline-first patterns, background sync, and fine-grained caching logic that goes beyond what HTTP headers can express.
When a page requests a resource, the browser checks these caches in priority order: service worker cache (if registered), memory cache, disk cache, and finally the network. A browser cache hit eliminates all network activity, including DNS resolution, TCP/TLS handshake, and data transfer. This makes browser caching the fastest possible delivery method, but also the least controllable from the server side.
What Is CDN Cache and How Does It Differ?
A CDN cache is a shared cache deployed on edge servers distributed across dozens to hundreds of geographic locations. Unlike the browser cache, which serves a single user, a CDN cache serves all users in a geographic region. When any user in that region requests a resource, the CDN edge can serve it from its cache without contacting the origin server.
| Metric | Edge Processing | Origin Processing | Improvement |
|---|---|---|---|
| Latency (p50) | 8–15 ms | 120–300 ms | 15–20x faster |
| Cache Hit Ratio | 85–95% | N/A | Reduces origin load |
| TLS Handshake | 1–5 ms (nearby PoP) | 50–150 ms | 10–30x faster |
| Bandwidth Cost | $0.01–0.04/GB | $0.05–0.12/GB | 3–5x cheaper |
| Availability | 99.99% (multi-PoP) | 99.9% (single DC) | 10x fewer outages |
CDN caching still requires a network round-trip from the user's browser to the nearest edge server, so it is inherently slower than a browser cache hit. However, CDN cache hits are significantly faster than origin fetches because edge servers are geographically close to users (typically within 20-50ms round-trip) compared to origin servers that may be hundreds of milliseconds away.
| Characteristic | Browser cache | CDN cache |
|---|---|---|
| Type | Private (single user) | Shared (all users in region) |
| Location | User's device | Edge server (PoP) |
| Latency on hit | ~0ms (memory) / ~1-5ms (disk) | ~10-50ms (network to edge) |
| Storage capacity | Hundreds of MB to few GB | Terabytes per edge server |
| Controlled by | max-age, private, no-store | s-maxage, public, CDN rules |
| Server-side purge | Not possible | Yes (API, tag, wildcard) |
| Survives browser close | Disk cache: yes; Memory: no | Yes (persists on edge server) |
How Do Cache-Control Headers Target Each Layer?
The Cache-Control header is the primary mechanism for controlling behavior at both layers. Different directives target different caches:
Directives That Affect Both Layers
max-age=N— Sets freshness duration for all caches (browser and CDN) unless overridden bys-maxageat the CDN layer.no-store— Prohibits all caches from storing the response. Neither browser nor CDN will cache it.no-cache— Both layers store the response but must revalidate before serving. Uses conditional requests with ETag or Last-Modified.
Directives That Target Only the Browser
private— Only the browser may cache the response. CDN edge servers and other shared caches must not store it.immutable— Tells the browser not to revalidate during page reload. CDN edges generally ignore this directive.
Directives That Target Only CDN (Shared Caches)
s-maxage=N— Overridesmax-agefor shared caches only. Browsers ignore it and usemax-ageinstead.proxy-revalidate— Forces shared caches to revalidate stale responses. Browsers ignore it.public— Explicitly permits shared caches to store the response, even if it was generated for an authenticated request.
The Split TTL Pattern
The most powerful application of layer-specific directives is the split TTL pattern:
Cache-Control: max-age=0, s-maxage=300, stale-while-revalidate=60
This header tells browsers to always revalidate (max-age=0), tells CDN edges to cache for 5 minutes (s-maxage=300), and allows edges to serve stale content for 60 seconds while refreshing in the background. This pattern is ideal for HTML pages and API responses where you want CDN offloading without risking stale content in uncontrollable browser caches.
How Do Headers Flow Through Both Cache Layers?
Understanding the header flow through the full request path is critical for debugging cache issues. Here is the complete flow for a cacheable resource:
First Request (Cold Caches)
- Browser checks local cache: miss.
- Browser sends request to CDN edge.
- CDN edge checks its cache: miss.
- CDN edge fetches from origin (possibly through L2/origin shield).
- Origin responds with headers:
Cache-Control: max-age=0, s-maxage=300,ETag: "abc123". - CDN edge caches the response for 300 seconds. Adds
X-Cache: MISS,Age: 0. - Browser receives response. With max-age=0, browser marks it as immediately stale but stores it for conditional revalidation.
Second Request (CDN Cache Hit)
- Browser checks local cache: stale (max-age=0). Sends conditional request with
If-None-Match: "abc123". - CDN edge checks its cache: hit (within 300-second s-maxage window).
- CDN edge responds with the cached copy. Adds
X-Cache: HIT,Age: 45. - Browser compares the response to its stale version. If unchanged, uses the local copy. If different, updates the local cache.
Headers Added by the CDN
CDN edge servers add diagnostic headers that are essential for debugging:
| Header | Purpose | Example values |
|---|---|---|
X-Cache | Cache hit or miss status | HIT, MISS, BYPASS, EXPIRED |
Age | Seconds since edge cached the response | 0, 45, 299 |
Via | Intermediate proxies in the chain | 1.1 varnish, 1.1 cloudfront |
X-Cache-Hits | Times served from this edge's cache | 1, 57, 1203 |
CF-Cache-Status | Cloudflare-specific cache status | HIT, MISS, DYNAMIC, BYPASS |
How Do You Debug Cache Issues with Browser DevTools?
Chrome DevTools and Firefox Developer Tools provide comprehensive visibility into cache behavior at both layers. Here is a systematic debugging workflow:
Step 1: Inspect Network Response Headers
Open DevTools, navigate to the Network tab, and reload the page. Click on a resource to inspect its response headers. Look for:
Cache-Control— Confirms the caching directives sent by origin/CDN.X-CacheorCF-Cache-Status— Shows whether the CDN served from cache.Age— Shows how long the response has been in CDN cache.ETag/Last-Modified— Confirms validation mechanisms are present.
Step 2: Check the Size Column
The Size column in DevTools reveals where the response came from:
- (memory cache) — Served from browser memory cache. No network request made.
- (disk cache) — Served from browser disk cache. No network request made.
- (ServiceWorker) — Served by a service worker. May or may not have used network.
- Numeric size (e.g., 42.3 kB) — Fetched from network (CDN edge or origin).
Step 3: Test Without Browser Cache
Check the "Disable cache" checkbox in DevTools to bypass the browser cache entirely. This lets you test CDN cache behavior in isolation. With browser cache disabled, every request goes to the network, and you can see whether the CDN is serving cached responses (X-Cache: HIT) or fetching from the origin.
Step 4: Test Without CDN Cache
Append a unique query parameter (e.g., ?bust=123) to bypass CDN cache and test origin response headers directly. This reveals the raw headers your origin is sending before any CDN processing.
What Are Common Pitfalls in Multi-Layer Caching?
The interaction between browser and CDN caches creates several traps that catch even experienced engineers:
- Long max-age with no cache busting — Setting
max-age=86400on HTML means users see day-old content from their browser cache, and CDN purges cannot fix it. Usemax-age=0for HTML and rely ons-maxagefor CDN caching. - Confusing CDN purge with browser cache clear — Purging the CDN cache does not clear browser caches. Users who already have the old version cached locally continue seeing it until their browser cache expires or they clear it manually.
- Setting private on public content — Using
Cache-Control: privateon content that is identical for all users prevents CDN caching entirely, forcing every request to the origin. This is correct for personalized content but wastes CDN capacity for public pages. - Ignoring service worker caching — A service worker can override both browser and CDN caching behavior. If your service worker caches aggressively, CDN header changes have no effect until the service worker is updated.
- Vary header fragmentation — Adding
Vary: CookieorVary: User-Agentcreates separate cache entries at the CDN for each unique value. With thousands of unique cookies or user agents, the CDN cache becomes fragmented and hit rates collapse. Use cache key normalization instead.
How Should You Design a Multi-Layer Cache Strategy?
An effective multi-layer strategy assigns the right TTL and directives to each layer based on content type and update frequency:
| Content type | Browser layer | CDN layer | Cache-Control header |
|---|---|---|---|
| Hashed static assets | 1 year | 1 year | public, max-age=31536000, immutable |
| HTML pages | Always revalidate | 5 minutes + SWR | max-age=0, s-maxage=300, stale-while-revalidate=60 |
| Public API | 30 seconds | 2 minutes | public, max-age=30, s-maxage=120 |
| User dashboard | 5 minutes | Bypass | private, max-age=300 |
| Auth tokens | Never cache | Never cache | no-store |
The fundamental principle is that browser cache TTLs should be shorter than or equal to CDN cache TTLs, because you can purge the CDN but cannot purge browsers. Give yourself a server-side escape hatch by keeping browser TTLs conservative while using the CDN layer for aggressive origin offloading.
Multi-layer caching is not a set-and-forget configuration. Monitor your cache hit ratios at both layers, use DevTools to verify real-world behavior, and adjust your headers as content patterns evolve. The right balance between browser and CDN caching reduces origin load by 90% or more while ensuring users always see fresh content when it matters.