Caching Is Not One Thing, It Is a Chain
When people say “my site has caching,” they often mean one of three very different things: browser cache, CDN cache, or server-side cache. These layers do not replace one another. They stack.
A request typically flows like this:
Browser → CDN → Origin Server → Application
Each layer independently decides whether it can reuse a previous response. A cache hit at any layer stops the chain. A miss pushes the request forward.
Most performance issues happen not because caching is absent, but because one layer invalidates another without anyone realizing it.
Why Browser Cache Is Often the Weakest Link
Browser caching relies almost entirely on HTTP headers. The browser does not “guess.” It follows rules.
Key headers include:
Cache-ControlExpiresETagLast-Modified
A single directive like no-store or no-cache overrides everything. Many frameworks set these headers defensively by default, especially on pages involving cookies, sessions, or authentication.
The result is that static-looking pages are treated as dynamic, forcing revalidation or full reloads on every visit.
The Hidden Cost of no-cache
A common misconception is that no-cache disables caching entirely. It does not.
no-cache means:
“You may store this response, but you must revalidate it with the server before using it.”
This triggers conditional requests using ETag or Last-Modified. While smaller than full downloads, these requests still introduce latency, especially on mobile networks.
For pages visited frequently, no-cache can be almost as expensive as no caching at all.
Why CDNs Ignore Your Intentions
CDNs do not blindly respect origin headers. They interpret them.
Some CDNs:
- Ignore
Set-Cookieresponses - Treat query strings as cache-busting by default
- Apply minimum TTLs regardless of headers
- Cache only specific content types
If your HTML response includes a tracking cookie, many CDNs will refuse to cache it unless explicitly configured. This causes full HTML generation on every request, even when content is identical.
From the developer’s perspective, caching is “on.” From the CDN’s perspective, the content is uncacheable.
Query Strings and the Cache Explosion Problem
A URL like:
/page?utm_source=google&utm_campaign=test
is technically a different resource from:
/page
Unless normalized, each variation becomes a separate cache entry.
Marketing parameters, tracking IDs, and dynamic tokens silently destroy cache hit rates. Some CDNs allow query string stripping or whitelisting, but this must be explicitly configured.
Without it, caching works perfectly in theory and fails completely in production.
ETag Mismatch Across Servers
In multi-server setups, ETag values often differ because they are generated using file metadata or server-specific values.
A browser revalidates using an ETag, but the CDN forwards the request to a different origin server, which responds with a different ETag. The browser treats this as a change and reloads the full content.
This leads to consistent cache misses even when files never change.
Disabling ETag or making it deterministic across servers is often necessary in distributed environments.
Why Logged-In Users Break Everything
Once a user is logged in, responses often include:
- Session cookies
- Personalized headers
- CSRF tokens
- User-specific content
Many applications mark all authenticated responses as non-cacheable. This is safe but expensive.
In reality, large portions of logged-in pages are identical across users. Without fragment caching or edge-side includes, entire pages bypass caching because of a few dynamic elements.
The result is poor performance for your most valuable users.
The Difference Between Freshness and Validity
Caching decisions depend on two concepts that are often confused:
- Freshness: can this response be used without checking?
- Validity: is this response still correct?
max-age controls freshness.
Revalidation controls validity.
Many systems rely too heavily on revalidation instead of allowing controlled freshness. This leads to constant background requests that erode performance under load.
Cache Invalidation Is Not the Main Problem
The famous saying “cache invalidation is hard” is only half the story.
In practice, most performance problems come from:
- Overly aggressive invalidation
- Zero TTL defaults
- Fear-driven configurations
- Misaligned layers making contradictory decisions
Caching fails not because invalidation is impossible, but because no one defines which layer is authoritative.
Why Performance Feels Random to Users
From the user’s perspective, performance appears inconsistent:
- Fast on first load, slow on refresh
- Fast on desktop, slow on mobile
- Fast yesterday, slow today
This randomness reflects cache fragmentation. Different layers hit or miss depending on headers, cookies, network path, and timing.
Without observability into cache headers and hit ratios, teams often optimize blindly.
What “Good Caching” Actually Looks Like
Effective caching is intentional, not accidental.
It requires:
- Explicit cache rules per content type
- Normalized URLs
- Clear separation of static and dynamic content
- Alignment between browser, CDN, and origin behavior
- Willingness to let content be temporarily stale
When these elements align, performance becomes predictable rather than magical.