← All postsProduct

A Native Astro Cache Provider for Canner — Plus Draft Mode and Stale-While-Revalidate

When Deven, an agency developer building on Canner, asked whether our cache could bypass itself for draft previews and whether we might expose an Astro cache provider, the honest answer was: one of those was a small gap, and the other was a chance to make Canner a first-class citizen in Astro's own caching system. This week both shipped — and stale-while-revalidate came along for the ride.

Taken together, they change how we talk about caching on Canner. It isn't a DatoCMS feature or an Astro feature. It's a framework- and CMS-agnostic caching system: mark a response cacheable, tag it, and invalidate it precisely — by content tag or by URL — from whatever CMS and framework you use.

A native Astro cache provider

Astro 7 introduced route caching: one platform-agnostic API — Astro.cache.set() — where each host plugs in a provider that translates your directives into its own caching. Astro ships providers for Netlify, Vercel, and Cloudflare. As of this week, @canner-ca/astro-cache adds Canner to that list.

Configure it once:

// astro.config.mjs
import { defineConfig } from 'astro/config';
import node from '@astrojs/node';
import { cacheCanner } from '@canner-ca/astro-cache';

export default defineConfig({
  adapter: node({ mode: 'standalone' }),
  cache: {
    provider: cacheCanner({
      slug: 'my-project',
      token: process.env.CANNER_CACHE_TOKEN,
    }),
  },
});

Then cache per route, with the same API you'd use on any other host:

---
// src/pages/blog/[slug].astro
const post = await getPost(Astro.params.slug);
Astro.cache.set({ maxAge: 3600, swr: 86400, tags: [post.id, 'blog-listing'] });
---

Because Canner caches at its own proxy — in front of your app and shared across every instance — the provider never spends your application's memory on an in-process cache. It sets the directives, and invalidate() purges by tag or by path through the same token.

Draft mode: preview on the real URL, without purging

The workflow that started this: an editor wants to see an unpublished draft on the actual page URL, but purging the cache to show them would serve a slow, uncached page to everyone else. The fix is to let a single request bypass the cache instead.

Every caching project now has a bypass secret. A request that carries it — in a __canner_bypass cookie, a ?__canner_bypass= link, or an X-Canner-Bypassheader — is rendered fresh from origin, while the public keeps getting the cached page. The draft response is never stored and never evicts the published one, so draft content can't leak into the public cache. It's the same shape as Vercel's Draft Mode, and the framework helpers give you a turnkey draft route:

// src/pages/api/draft.ts
import { enableDraft } from '@canner-ca/astro-cache';

export const GET = ({ url, cookies, redirect }) => {
  if (url.searchParams.get('secret') !== import.meta.env.PREVIEW_SECRET) {
    return new Response('Unauthorized', { status: 401 });
  }
  enableDraft(cookies, import.meta.env.CANNER_BYPASS_SECRET);
  return redirect(url.searchParams.get('to') ?? '/');
};

The same helpers ship for Next.js and Nuxt. By default the secret must match (strict mode); an opt-in mode bypasses on the cookie's presence alone, for apps that already gate their own draft session.

Stale-while-revalidate: nobody waits on a cold render

The old behaviour was simple and a little unforgiving: once a page's TTL elapsed, the next visitor triggered a fresh render and waited for it. Now, if you set a stale-while-revalidate window, Canner serves the stale copy instantly past the TTL and refreshes it once in the background. The refresh is single-flighted, so a burst of traffic at the moment of expiry triggers exactly one origin request — not one per visitor.

Turn it on by adding the directive to your Cache-Control, or swr to any of the helpers:

cache(Astro.response, { ttl: 3600, swr: 86400, tags: [post.id] });
// → Cache-Control: public, s-maxage=3600, stale-while-revalidate=86400

Invalidate by tag or by URL

Tags remain the right tool when a piece of content appears on many routes: tag each response with the entity's id, and one purge clears them all. But the Astro provider tracks routes, so it also needs to purge by exact URL — and now you can, from any CMS webhook:

curl -X POST https://api.canner.ca/projects/<slug>/cache/purge \
  -H "Authorization: Bearer <purge token>" \
  -H "Content-Type: application/json" \
  -d '{"paths": ["/blog/my-post"]}'

A bare path clears every query variant of it. Send tags, paths, or both in one request.

One system, any stack

None of this is tied to a particular CMS or framework. The provider is Astro's native API; the header helpers work anywhere you can set a response header; draft mode and SWR are properties of the cache itself. DatoCMS is one worked example in the guide — not the frame.

And it all runs on the same Canadian infrastructure as your app: the cache, the purge, and the draft render never leave the country. If you're on Canner, enable caching under Settings → Caching and point your framework at it. If you're weighing a move, that's the whole pitch — fast, sovereign, and now with a caching system that meets your stack where it is.

About the author

Colin Shand is the founder of Canner, a Canadian deployment platform operated from Quebec. He writes about sovereign infrastructure, the Canadian startup ecosystem, and building independently.

Try Canner.

Drop a project, get a live URL on Canadian infrastructure in about 30 seconds. Free tier available.