Dynamic Content Caching: When and How to Cache the Uncacheable
Dynamic content has traditionally been considered uncacheable. Pages that change based on the user, the time of day, inventory levels, or real-time data appear to require a fresh origin response for every request. This assumption leaves enormous performance gains on the table. In reality, most dynamic content changes far less frequently than the request rate demands, and a range of techniques exist to cache it at the CDN edge without serving stale or incorrect data. From micro-caching to Edge Side Includes, these approaches transform high-cost origin renders into low-latency edge responses.
What Counts as Dynamic Content?
Dynamic content is any response generated at request time by application logic rather than served from a static file. The spectrum ranges from slightly dynamic to fully personalized: For foundational context, read CDN Caching Strategies: Pull, Push, and Hybrid — FlyFish NWA.
| Content type | Change frequency | Personalized | Cacheability |
|---|---|---|---|
| Blog homepage with latest posts | Every few minutes | No | High with short TTL |
| Product listing page | Minutes to hours | No | High with short TTL |
| Search results | Real-time | By query | Medium, key by query |
| Shopping cart | Per interaction | Per user | Low, bypass or fragment |
| Dashboard with live metrics | Seconds | Per user | Low, micro-cache only |
| Checkout and payment | Per interaction | Per session | None, always bypass |
The key insight is that most dynamic content is not personalized. A product listing page showing the same items to every visitor is technically dynamic because the server renders it from a database query, but the output is identical for all users. This content is an immediate candidate for edge caching with an appropriate TTL strategy.
What Is Edge Side Includes and How Does It Enable Fragment Caching?
Edge Side Includes is a markup language that allows CDN edge servers to assemble pages from independently cacheable fragments. Instead of caching an entire page as one unit, ESI breaks it into components, each with its own TTL and cache key.
| 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 |
The origin sends a response containing ESI include directives:
<!-- Page shell cached for 1 hour -->
<header>
<esi:include src="/fragments/nav" />
</header>
<main>
<h1>Product Details</h1>
<div class="product-info">...static content...</div>
<div class="pricing">
<esi:include src="/fragments/price?sku=ABC123" />
</div>
<div class="reviews">
<esi:include src="/fragments/reviews?sku=ABC123" />
</div>
</main>
Each fragment can have independent cache settings:
- Navigation fragment: Cached for 24 hours, same for all users.
- Price fragment: Cached for 60 seconds, changes with promotions.
- Reviews fragment: Cached for 5 minutes, updates as reviews are posted.
The CDN fetches each fragment independently, assembles the complete page, and serves it to the user. If the navigation is cached but the price fragment has expired, only the price fragment triggers an origin fetch. This granularity maximizes cache efficiency while maintaining freshness where it matters.
ESI is supported by Varnish, Akamai, Fastly, and several other CDN providers. For a deeper understanding of ESI implementation patterns, see the Edge Side Includes guide.
How Does Micro-Caching Work for High-Traffic Dynamic Pages?
Micro-caching stores responses for very short durations, typically one to five seconds. The technique is deceptively powerful. For a page receiving 1,000 requests per second, a one-second micro-cache means 999 of those requests are served from cache and only one reaches the origin.
Configure micro-caching in Nginx:
proxy_cache_path /var/cache/nginx/micro levels=1:2
keys_zone=micro:10m max_size=1g inactive=1m;
location /api/feed {
proxy_cache micro;
proxy_cache_valid 200 1s;
proxy_cache_use_stale updating;
proxy_cache_lock on;
proxy_pass http://app_server;
}
The combination of a one-second cache, proxy_cache_use_stale updating, and proxy_cache_lock on creates a powerful pattern. The lock ensures only one request per second reaches the origin. The updating stale directive serves the previous response while the new one is being fetched, eliminating any latency spike at the cache boundary. This is essentially stale-while-revalidate at the proxy level.
Micro-caching works best for pages where one-second staleness is acceptable:
- News feeds and timelines: A one-second delay in showing a new post is imperceptible.
- Product listings: Inventory updates within one second are fast enough for most e-commerce scenarios.
- API endpoints: Public API responses that aggregate data can tolerate brief staleness in exchange for dramatic latency reduction.
How Can You Cache Personalized Content at the Edge?
Personalization is the hardest dynamic content to cache because the response varies per user. Several strategies address this challenge, each with different tradeoffs:
Edge-Side Assembly
Separate the shared page shell from personalized elements using ESI. Cache the shell for hours and fetch personalized fragments per request. The shell, which includes layout, navigation, product details, and images, represents 80-90% of the page payload. Only the personalized fragments such as user name, cart count, and recommendations need per-request fetching.
Client-Side Personalization
Cache the complete generic page at the CDN and load personalized elements via browser-side API calls after the page renders. This approach is increasingly common in modern single-page applications:
- CDN serves a cached, generic product page instantly.
- After page load, the browser fetches
/api/user/recommendationsand/api/user/cart-count. - These API calls either bypass CDN cache or use short TTLs.
- The browser injects personalized content into the already-rendered page.
The user perceives a fast page load because the bulk of the content arrives from the edge cache. Personalized elements appear moments later, often before the user even notices the delay.
Cookie-Based Cache Bypass
The simplest personalization strategy bypasses the CDN cache entirely for logged-in users while serving cached content to anonymous visitors. Since anonymous traffic typically represents 70-90% of total traffic on most sites, this approach still provides substantial origin protection.
# Varnish VCL: bypass cache for logged-in users
sub vcl_recv {
if (req.http.Cookie ~ "session_id") {
return (pass);
}
}
This is effective but leaves significant optimization potential untapped. Logged-in users still benefit from edge proximity for TLS termination, connection pooling, and content routing, but their HTML responses always come from the origin.
What Is the Short TTL Plus SWR Pattern?
Combining short TTLs with stale-while-revalidate is the most versatile pattern for dynamic content caching. The short TTL ensures content stays reasonably fresh, while SWR eliminates the latency penalty of cache misses:
Cache-Control: public, max-age=10, stale-while-revalidate=50
This header tells the CDN to serve content from cache for 10 seconds. Between 10 and 60 seconds, serve the stale version instantly while fetching a fresh copy in the background. The user never waits for an origin response, and the content is never more than 60 seconds old.
For a page with 500 requests per second, this pattern reduces origin load from 500 to approximately one request every 10 seconds while keeping content no more than a minute stale. Configure appropriate Cache-Control headers to implement this at scale.
The short TTL + SWR pattern also naturally prevents cache stampedes. Because SWR triggers a single background revalidation rather than allowing all requests to hit the origin when the TTL expires, the origin never sees a traffic spike at the cache boundary.
How Do You Implement A/B Testing with Edge Caching?
A/B testing introduces a variant dimension to caching. Each user should consistently see the same variant, but the CDN should cache each variant separately rather than sending every request to the origin.
The recommended approach uses CDN edge logic to assign and persist variants:
- The edge checks for an existing variant cookie on the request.
- If no cookie exists, the edge assigns a variant based on a hash of the user identifier or a random selection.
- The edge sets the variant as part of the cache key, creating separate cached versions for variant A and variant B.
- The response includes a
Set-Cookieheader to persist the variant assignment.
This approach ensures that the CDN caches two versions of each page, one per variant, rather than bypassing cache entirely for A/B testing. The origin only needs to generate each variant once per TTL per edge PoP.
How Do API Responses Benefit from Edge Caching?
API responses are often treated as entirely uncacheable, but most API traffic falls into patterns that benefit from caching:
| API pattern | Caching approach | Recommended TTL |
|---|---|---|
| Public catalog or listing | Standard cache with SWR | 30 seconds to 5 minutes |
| Search with query parameters | Cache keyed by query string | 60 seconds to 5 minutes |
| User profile or preferences | Cache per user token | 5 to 60 seconds |
| Real-time pricing or inventory | Micro-cache | 1 to 5 seconds |
| Write operations (POST, PUT, DELETE) | Never cache | Not applicable |
For authenticated API responses, include the authorization token or user identifier in the cache key. This creates per-user cache entries that can still serve repeated requests from the same user without hitting the origin. Be mindful of cache key fragmentation, though. If every user has a unique cache entry, the effective hit ratio may be too low to justify the storage overhead.
What Are the Risks of Caching Dynamic Content?
Caching dynamic content carries risks that do not exist with static assets:
- Serving stale data: Price changes, inventory depletion, or content corrections may not appear until the cache expires. Set appropriate TTLs and implement cache invalidation for critical updates.
- Cross-user data leaks: If personalized content is accidentally cached without user-specific cache keys, one user might see another user's data. This is a security vulnerability, not just a UX issue. Always verify that cache keys include user identifiers for personalized responses.
- Cookie pollution: Responses with
Set-Cookieheaders cached at the edge can set cookies for the wrong users. Most CDNs stripSet-Cookiefrom cached responses by default, but verify this behavior. - Cache poisoning: If the cache key does not include all request parameters that affect the response, an attacker can inject malicious content into the cache. Audit your cache keys to ensure they capture all response-varying inputs.
Despite these risks, the performance benefits of caching dynamic content are substantial. A well-configured dynamic caching strategy typically improves your cache hit ratio by 20-40 percentage points while maintaining acceptable content freshness. The key is understanding which content can tolerate staleness, decomposing pages into cacheable fragments, and using short TTLs with SWR to minimize the freshness gap. Combined with proper cache hierarchy design, dynamic content caching transforms your CDN from a static asset server into a full-stack performance layer.