I remember the first time I tried to cache dynamic content at the edge. Our news site was getting hammered, and I thought, 'How hard can it be?' I set a long TTL on everything. Big mistake. Users saw outdated headlines for hours. That was a painful lesson.
So, can you cache dynamic content at the CDN edge? Absolutely. But it's not as simple as flipping a switch. You need to control the cache key and freshness rules. Get it wrong, and you'll serve stale or wrong data. Get it right, and you'll reduce origin load and speed up your site.
Why the Default CDN Behavior Often Falls Short
Most CDNs are conservative by default. Cloudflare, for instance, only caches certain status codes when you don't send cache headers. 200s get cached for 120 minutes, 302s for 20 minutes, and 404s for 3 minutes. Everything else? Not cached. So if your origin doesn't send explicit Cache-Control headers, you might get caching for two hours—or none at all, depending on the CDN. But the bigger issue is mindset. Many developers think dynamic content is uncacheable, so they slap on Cache-Control: no-store or private. That's like throwing away free performance. and I've been guilty of it too.
Think of your caching layers: browser, CDN edge, load balancer, app cache, Redis, then the database. Skipping the CDN means you're missing a huge opportunity. AWS says CDN cache hit ratios often hit 80-95%, and CDNs can absorb 70% or more of origin requests. So it's not about whether to cache; it's about how to do it without shooting yourself in the foot.
Control the Cache Key with Vary—But Don't Go Overboard
The Vary header tells caches which request headers affect the response. If your page changes based on Accept-Encoding or User-Agent, you need to list those. For example, Vary: Accept-Encoding is standard for compressed responses. But Vary: User-Agent is a cache killer. There are thousands of user agents out there, so you'll end up with a separate cache entry for each one. That's a recipe for a low hit ratio.
Instead, use a custom header like X-Device-Type that you set at the edge or in your app. And avoid Vary: *—that makes the response uncacheable. Rule of thumb: keep the cache key as small as possible. For logged-in users, don't cache at the edge. Use Cache-Control: private and let the browser handle it, or use a separate cache key based on a cookie.
Stale-While-Revalidate: Your Secret Weapon
stale-while-revalidate (from RFC 5861) is a big deal. It lets caches serve a stale response for a set number of seconds while they fetch a fresh one in the background. Users never wait. For example, Cache-Control: max-age=60, stale-while-revalidate=300 means the response is fresh for 60 seconds, then for up to 300 seconds, the cache can serve the stale copy while revalidating. Perfect for content that updates often but where a few seconds of staleness won't hurt.
There's also stale-if-error, which lets caches return a stale response if the origin returns a 500 or a DNS failure. Combine these, and you'll see a big drop in origin load and happier users. But you have to set them. Don't just rely on max-age.
| Approach | When to use | Cache key considerations | Staleness tolerance |
|---|---|---|---|
| Cache everything with short TTL | Low-risk pages (e.g., homepage, popular articles) | Vary on Accept-Encoding only | Up to TTL (e.g., 60s) |
| Cache with stale-while-revalidate | Frequently updated but not critical | Vary on Accept-Encoding, maybe a custom header | Stale for up to 300s while revalidating |
| No edge caching (private) | User-specific data (cart, account) | Not cached at edge | None |
Invalidate Aggressively When Content Changes
Even with a perfect cache key, you need a way to purge stale content. Cloudflare has purge by single-file, which instantly removes a cached resource from all data centers. That's your emergency button. But don't rely on manual purges for every update. Instead, design your app to send a purge request when content changes, or use versioned URLs (e.g., /article/123?v=2). Also, RFC 9111 says unsafe methods like POST, PUT, DELETE can invalidate stored responses. So if your API uses those, caches should invalidate the target URI automatically. But not all caches do, so explicit purging is safer.
For dynamic content, I recommend a two-pronged approach: short TTLs (e.g., 60 seconds) for most pages, and immediate purges for critical updates like breaking news. In one project, we set up a system where editing an article triggered a purge via API. It cut down stale content complaints by 90%. That's the kind of win you want.
- Set
Cache-Control: public, max-age=60, stale-while-revalidate=300for article pages. - Use
Vary: Accept-Encodingonly; avoidUser-Agent. - Purge by URL when an article is edited.
- For logged-in users, use
Cache-Control: privateand cache in the browser.
So, caching dynamic content at the edge isn't just possible—it's essential for performance. The key is to control the cache key with a minimal Vary, set appropriate freshness with max-age and stale-while-revalidate, and have a solid invalidation strategy. Do this, and you'll absorb the majority of traffic at the edge, just like the 80-95% hit ratios AWS reports. Stop treating dynamic content as uncacheable; treat it as cacheable with rules.
Sources
- MDN (Cache-Control) - https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control
- RFC 5861 (stale-while-revalidate) - https://www.rfc-editor.org/rfc/rfc5861.txt
- Cloudflare (default cache behavior) - https://developers.cloudflare.com/cache/concepts/default-cache-behavior/
- AWS (caching overview) - https://aws.amazon.com/caching/
- MDN (Vary) - https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Vary
- Cloudflare (purge by single-file) - https://developers.cloudflare.com/cache/how-to/purge-cache/purge-by-single-file/
Comments (0)
Please sign in to post a comment.
Don't have an account? Create one
No comments yet. Be the first to comment!