I thought I had one cache. I actually had four.
I recently migrated my personal blog from Vercel to Cloudflare Pages + Workers. That migration deserves its own post, but the short version is: I realised I didn't need two platforms when Cloudflare could handle CI/CD, DNS, edge compute, and caching in one place.
The site started life on Gatsby, moved to Next.js, and was deployed to Vercel. It was great: a repo push, a few clicks, and my site was live. Later, I added Cloudflare in front as an edge CDN, then moved my domains there as well. Then I moved to SvelteKit, and that was super awesome (and still is!). Over time, it felt weird to have Vercel and Cloudflare both doing bits of the same job, so I decided to consolidate everything onto Cloudflare.
That's where the fun started.
Why I added KV in front of Notion
I use Notion as my CMS. Posts live in a Notion database, and I fetch them via the Notion API with
the @notionhq/client. Notion has rate limits, and my publishing model is simple:
- Once a post is published, it almost never changes.
- There is no reason to hit Notion on every page view (which is also slow).
So I introduced a 4‑layer caching model:
- Cloudflare KV – 30‑day TTL for Notion responses (lists and posts).
- Cloudflare CDN HTTP cache – initially enabled, later disabled for dynamic pages.
- SvelteKit navigation cache – client-side
__data.jsonused for SPA navigations and prefetch. - Browser cache – normal HTTP caching semantics.
The idea:
- First visitor to a given list or post → Worker calls Notion, then writes the result into KV.
- Subsequent visitors → Worker reads from KV at the edge, no Notion call.
- SvelteKit's prefetch makes navigation feel instant: hover a menu item, and it preloads
__data.jsonin the background.
On paper, it looked perfect: a fast stack where the Notion API is mostly untouched, and both lists and posts are served from the edge.
The bug: new posts that refused to appear
Then I published a test post, the KV cache cleared as expected, Cloudflare's cache said everything was fresh, but the live site showed only old posts.
So, the Home page sometimes showed the new post after a hard refresh. My News archive would stubbornly show only the old list. I refreshed the archive, and it showed the new post, but as soon as I navigated away and back, it showed the old list only. On mobile, even refreshing sometimes didn't help.
At this point, I had the classic feeling: "one of these four caches is lying to me".
I tried the obvious things first:
- Headers:
Cache-Control: no-store, ETag based on a content version,CDN-Cache-Controltweaks. - Cloudflare: explicit purging of KV and all relevant URLs (
/,/news-archive, their__data.jsonvariants). - SvelteKit flags:
depends('content:lists')on the relevant routes plus invalidation flows. - Prefetch off: disabling prefetch for
/news-archivelinks so hovering wouldn't preload stale__data.json.
Some combinations worked sometimes, but never reliably. The most annoying pattern:
1 | |
2 | |
3 | |