Astro · npm

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.

$npm install @canner-ca/astro-cache

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.