A native cache provider for Astro.
@canner-ca/astro-cache plugs Canner into Astro 7's built-in route caching as a first-class provider — the same API you'd use on Netlify, Vercel, or Cloudflare — plus draft mode and one-line cache headers. Tag- and path-based purging, stale-while-revalidate, zero dependencies.
The cache provider (Astro 7+)
Configure Canner once as your cache provider, then control caching per route with Astro.cache.set(). Canner caches at its proxy — in front of your app, shared across instances — so it never spends your app's memory on an in-process cache, and invalidate() purges by tag or path.
// astro.config.mjs
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, in any page or endpoint:
Astro.cache.set({ maxAge: 3600, swr: 86400, tags: [post.id] });Draft mode
Preview unpublished content on its real URL without purging the published page. enableDraft() sets a hardened cookie carrying your bypass secret; Canner renders that visitor fresh from origin while everyone else keeps the cached page. Gate the route yourself, then check isDraft() to fetch draft content.
// 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') ?? '/');
};Prefer per-route headers?
Call cache() in any server-rendered route with a TTL and the tags that identify the content on the page. That's the whole integration on the code side.
---
// src/pages/blog/[slug].astro
import { cache } from '@canner-ca/astro-cache';
const post = await getPost(Astro.params.slug);
cache(Astro.response, { ttl: 3600, swr: 86400, tags: [post.id, 'blog-listing'] });
---Which sets
Cache-Control: public, s-maxage=3600, stale-while-revalidate=86400 Surrogate-Key: <post.id> blog-listing
When the post is published, your DatoCMS webhook purges its record id (Canner reads the changed record automatically) and Canner clears every page carrying that tag. Add a stable tag like blog-listing to your index page to clear it too.
Options
ttlRequired. Seconds Canner may serve the cached response — sets s-maxage.swrOptional. Stale-while-revalidate window in seconds — serve stale instantly past ttl while refreshing once in the background.tagsString, number, or array. Sets Surrogate-Key. Numbers are coerced; duplicates and whitespace tags are dropped.browserTtlOptional. Seconds the visitor's browser may cache (sets max-age). Omit to keep tag purges instant.What Canner caches
A response is cached only when all of these hold — the same rules this helper produces:
- GET or HEAD, status 200
- Cache-Control: public with a positive s-maxage (or max-age)
- no Set-Cookie
- Vary absent or only Accept-Encoding
- body under 8 MB
It only ever adds headers
The helper never strips or mutates anything your app set. It does not remove Set-Cookie — Canner already declines to cache a response that sets a cookie, so if you mark such a route cacheable you get a development-only warning, and your cookie is left untouched. A bad TTL sets no headers and warns; only passing something that isn't a Response or Headers throws.
Then point your CMS at Canner
The code side is done. The other half is two copy-paste values — the webhook URL and an Authorization header — shown pre-filled in the dashboard under Settings → Caching. No request body needed; Canner reads the DatoCMS payload automatically. Full caching guide.
Source and issues: tools/astro-cache/ in the canner repo on GitHub. Licensed MIT.