Goal
Always serve content from a CDN EDGE cache, regardless of how stale. Refresh it in the background when possible.
Problem
I have a NextJS app that renders some React components server-side and delivers them to the client. For this discussion, let's just consider my homepage, which is unauthenticated and the same for everyone.
What I'd like is for the server rendered homepage to be cached at a CDN's EDGE nodes and served to end clients from that cache as often as possible, or always.
From what I've read, CDNs (like Fastly) which properly support cache related header settings like Surrogate-Control
and Cache-Control: stale-while-revalidate
should be able to do this, but in practice, I'm not seeing this working like I'd expect. I'm seeing either:
- requests miss the cache and return to the origin when a prior request should have warmed it
- requests are served from cache, but never get updated when the origin publishes new content
Example
Consider the following timeline:
[T0] - Visitor1 requests www.mysite.com
- The CDN cache is completely cold, so the request must go back to my origin (AWS Lambda) and recompute the homepage. A response is returned with the headers Surrogate-Control: max-age=100
and Cache-Control: public, no-store, must-revalidate
.
Visitor1 then is served the homepage, but they had to wait a whopping 5 seconds! YUCK! May no other visitor ever have to suffer the same fate.
[T50] - Visitor2 requests www.mysite.com
- The CDN cache contains my document and returns it to the visitor immediately. They only had to wait 40ms! Awesome. In the background, the CDN refetches the latest version of the homepage from my origin. Turns out it hasn't changed.
[T80] - www.mysite.com
publishes new content to the homepage, making any cached content truly stale. V2 of the site is now live!
[T110] - Visitor1 returns to www.mysite.com
- From the CDNs perspective, it's only been 60s since Visitor2's request, which means the background refresh initiated by Visitor2 should have resulted in a <100s stale copy of the homepage in the cache (albeit V1, not V2, of the homepage). Visitor1 is served the 60s stale V1 homepage from cache. A much better experience for Visitor1 this time!
This request initiates a background refresh of the stale content in the CDN cache, and the origin this time returns V2 of the website (which was published 30s ago).
[T160] - Visitor3 visits www.mysite.com
- Despite being a new visitor, the CDN cache is now fresh from Visitor1's most recent trigger of a background refresh. Visitor3 is served a cached V2 homepage.
...
As long as at least 1 visitor comes to my site every 100s (because max-age=100
), no visitor will ever suffer the wait time of a full roundtrip to my origin.
Questions
1. Is this a reasonable ask of a modern CDN? I can't imagine this is more taxing than always returning to the origin (no CDN cache), but I've struggled to find documentation from any CDN provider about the right way to do this. I'm working with Fastly now, but am willing to try any others as well (I tried Cloudflare first, but read that they don't support stale-while-revalidate
)
2. What are the right headers to do this with? (assuming the CDN provider supports them)
I've played around with both Surrogate-Control: maxage=<X>
and Cache-Control: public, s-maxage=<X>, stale-while-revalidate
in Fastly and Cloudflare, but none seem to do this correctly (requests well within the maxage timeframe dont pickup changes on the origin until there is a cache miss).
3. If this isn't supported, are there API calls that could allow me to PUSH content updates to my CDN's cache layer, effectively saying "Hey I just published new content for this cache key. Here it is!"
I could use a Cloudflare worker to implement this kinda caching myself using their KV store, but I thought I'd do a little more research before implementing a code solution to a problem that seems to be pretty common.
Thanks in advance!