Edge Caching: CDN Strategies, Purge Patterns, and Personalization Trade-offs
Edge Caching stores responses near users. Learn CDN cache keys, purge patterns, and when personalization should bypass the edge so you stay fast and correct.
Edge Caching keeps a copy of a response close to the user. When the copy is fresh, the request never reaches your origin. That cuts latency and shields the core from load. If the copy is stale or private, you serve the wrong page, and the speed win is not worth the bug.
What It Is and Why It Fails
A CDN is a set of caches in front of your origin. The edge stores a response under a cache key and serves it until the time to live ends. RFC 9111 defines how HTTP caches may store, reuse, and revalidate a response.
Your CDN adds its own rules on top. If those rules disagree with your headers, you will debug the wrong layer.
Edge Caching fails in production for three common reasons. The cache key is too wide, so one user’s page is reused for another. The key is too narrow, so you never hit. Or the purge path is slow, so a price change stays on the edge after the database is correct.
Personalization makes this worse. A cookie, a geo header, or an experiment flag can split the cache into millions of entries. Hit rate falls.
Origin load returns. Meanwhile you still pay for the CDN. A common mistake I have seen is caching a page that includes the user’s name because the public shell looked static.
Another failure is the stampede. A hot key expires, and every edge misses at once. The origin sees a spike that looks like a traffic attack.
If you then scale the origin, you pay for capacity you only needed for one second. Stale while revalidate, plus a single origin shield, breaks that spike.
Cache Keys and Vary
The cache key is the identity of the object. Include the host and the path. Include only the query parameters that change the body.
Drop tracking parameters, or every ad click becomes a miss. Also drop cookies from the key unless the cookie truly changes the public response.
Vary tells a shared cache which request headers change the response. Vary on Accept-Encoding is normal. Vary on Cookie or Authorization usually means you should not cache that route at the edge. If you vary on a header the client sends freely, an attacker can fill the cache with junk keys.
Normalize before you hash the key. Sort query parameters. Lowercase the host.
Strip a trailing slash if your app treats both forms as the same page. If you skip this, you store two copies and purge only one. Users then see a mix of old and new.
Freshness and Stale Serving
Set a short browser TTL and a longer edge TTL when the HTML shell is public. The browser stays easy to refresh. The edge still absorbs repeats.
s-maxage controls shared caches. max-age controls the browser. If you set only max-age, some edges will treat it as their own limit.
Stale while revalidate lets the edge serve the old object while one request refreshes it. Stale if error lets you serve the old object when the origin is down. Both are useful.
Both can also hide a bad deploy, because users keep seeing the last good copy. Cap the stale window so a broken origin cannot live for hours.
Architecture and Implementation
Split traffic into three classes. Public and cacheable, private and never cached, and mixed pages where only a fragment is personal. Do that split in the app, not in a hope that the CDN will guess.
Send Cache-Control and a surrogate key from the origin. Then the edge has an explicit contract.
Amazon CloudFront and similar CDNs can sit in front of an API or a site. Put an origin shield, or one regional cache, between the edges and the app. Many pops then share one fill.
That protects a small origin. It also adds a hop, so a miss is slightly slower. For a hot catalog, the shield is worth it.
When a miss still hurts, scale the origin on misses, not on total user traffic. Autoscaling backend systems should watch origin request rate and latency. If the edge hit rate drops after a purge, the origin will spike even though users did not grow. The scaler needs that signal.
For a global product, decide which region fills the cache. Multi-region deployments can make each edge talk to a nearby origin. That lowers fill latency. It also means a purge must reach every region, or users in one place see new data while others see old data.
Purge Patterns
Purging by URL is precise and slow to fan out if you have many URLs. Purging by surrogate key, or tag, is the pattern I trust for catalogs. The origin tags a response with product and collection ids.
When the price changes, you purge those tags. You do not need the full URL list.
- Tag every cacheable response with a stable key the writer already knows.
- Keep a hard purge for one URL when a single object is wrong.
- Ban a soft purge that marks the object stale but still servable during refresh.
- Retry a failed purge, because a partial purge is worse than a slow one.
- Log the purge id, the key, and the time the last edge ack arrives.
- Alert when purge lag exceeds the product promise, not on every purge call.
Trade-offs You Should Name
A long TTL is cheap and fast until the data must change. A short TTL is safe and closer to a proxy. Personalization at the edge is flexible and easy to get wrong.
Bypass is simple and pushes load to the origin. Pick the class of page first. Then pick the TTL.
| Approach. | Best fit. | Main risk. | Origin load. |
|---|---|---|---|
| Long edge TTL. | Images and public files. | Slow to correct a bad file. | Very low. |
| Short TTL plus stale. | Catalog and content. | Users see slightly old data. | Low, with bursts. |
| Tag purge. | Prices and inventory. | Missed tag, stale page. | Spikes on purge. |
| Bypass on cookie. | Account and checkout. | Cookie set too wide. | All of that route. |
Negative caching is a separate trade. Caching a 404 for a few seconds stops a missing asset from hammering the origin. Caching it for minutes hides a new file you just shipped.
I use a short negative TTL, on the order of a few seconds, as an illustrative production range. Do not negative-cache 500 responses for long, or you will stick an outage in the edge.
Pitfalls and Failure Modes
The worst bugs are silent. The page is fast, the charts look green, and one user sees another user’s cart. Treat cache correctness as a product test, not only as a latency project. After any change to cookies or Vary, replay two users through the same URL.
- Putting the session id in the cache key and calling the result a hit-rate win.
- Forgetting a query parameter that changes the body, so users share the wrong page.
- Purging the origin path but not the edge host or the trailing-slash twin.
- Letting a Set-Cookie on a public response disable the cache for everyone.
- Serving stale personalized fragments after logout because the shell was cached.
- Using a purge API without a timeout, so a hung purge blocks the deploy.
Auth headers leak in odd ways. If a public response was stored, and a later request sends Authorization, a bad edge may still return the public object. That is fine only when the object is truly public.
If the route is mixed, bypass when Authorization or a session cookie is present. Test that bypass. Do not assume the vendor default matches your threat model.
Failover changes the picture. If failover architecture moves the origin to another region, cached objects may still be valid. They may also point at host names that died.
Include the origin identity in the key only when the body depends on it. Otherwise a failover will look like a full cache flush and the new origin will melt.
A Config You Can Start From
The snippet below is an illustrative rule for a public catalog API. It ignores cookies, allows two query parameters, and tags the object for purge. It is not a full CDN config.
Map the field names to your vendor. Keep the intent: a narrow key, a modest TTL, and an explicit purge tag.
match:
path_prefix: /api/catalog/
cache_key:
include: [host, path]
query_allow: [page, page_size]
drop_headers: [cookie, authorization]
ttl_seconds: 120
stale_while_revalidate_seconds: 30
stale_if_error_seconds: 60
negative_ttl_seconds: 5
purge_keys:
- catalog
bypass_if_header:
- authorization
Pair the rule with a response header from the app, such as Cache-Control and a surrogate key. If the app and the CDN disagree, the CDN rule should be the stricter one for private routes. For public routes, let the app be the source of truth so a developer can see the contract in the handler. Review the rule when you add a new query parameter.
Performance, Scale, and Cost
Hit rate is the main performance lever. A jump from a low hit rate to a high one can remove most origin reads. Measure hit rate by route, not as one global number. A global average hides a checkout path that never hits and a static path that always does.
Cost follows bytes and requests. Cost optimization should compare CDN egress with origin compute. A cache that stores huge uncacheable responses wastes edge storage and still misses.
Compress text. Cache images at the edge with a long TTL and a content hash in the URL, so you never purge them.
Fill storms are the scale failure. When you deploy, do not purge the world if a versioned asset URL would do. When you must purge a hot tag, warm the shield before you expire every pop.
Otherwise the origin sees one request per pop at the same moment. That is how a small price update becomes an incident.
Watch purge lag as a user-facing metric. If the business promise is that a price changes within a minute, the SLO is on purge lag, not on edge CPU. Also watch error rate on misses.
A fast hit with a failing origin is not a healthy system. The edge is hiding the failure until the object expires.
At very large key counts, memory at the edge evicts useful objects. A wide Vary or a user id in the key causes that. Tighten the key before you buy a bigger cache.
Eviction of hot keys looks like random latency. It is usually a key design bug.
Key Takeaways
- Cache only responses that are safe for more than one user.
- Keep the cache key narrow, and drop tracking query parameters.
- Use surrogate keys so a write can purge a set of URLs.
- Prefer stale while revalidate over a stampede of simultaneous misses.
- Bypass the edge for session, checkout, and anything with Authorization.
- Measure hit rate, purge lag, and origin load per route.
FAQ
Should HTML be cached at the edge?
Cache HTML when it is the same for many users. A marketing page or a public article is a good fit. A page with a name, a cart count, or a private price is not. If you need both, cache the public shell and fill the private bits from an uncached call.
How do you purge without missing URLs?
Tag the response at write time with ids you already store. Purge the tag, not a hand-built URL list. Also purge the exact URL when support needs one object fixed. Then check purge lag before you tell the business the change is live.
What TTL should you start with?
Start short, such as one or two minutes for public API data, and longer for hashed static files. Lengthen the TTL only after purge is reliable. If you cannot purge, keep the TTL inside the window users will accept for stale data.
Can you cache API responses with cookies?
Yes, if the cookie does not change the body. Drop the cookie from the key and from Vary. If the cookie selects a user or a tenant, do not cache that response at a shared edge. A private cache in the browser is a different choice, and it still must not leak across users.
List your top routes by origin load. Mark each as public, private, or mixed. For the public ones, set a narrow cache key, a short TTL, and a purge tag before the next launch.
Then run two test users against the same URL and confirm they cannot see each other’s data. When that holds, watch hit rate and purge lag for a week before you extend the TTL.
Last updated on 20 September 2026.