@canner-ca/next-cache
v0.1.0
Published
One-line cache headers for Canner — set Cache-Control + Surrogate-Key in Next.js Route Handlers, Middleware, and getServerSideProps so Canner caches and purges your pages by tag.
Maintainers
Readme
@canner-ca/next-cache
One-line cache headers for Canner, for Next.js. It sets
Cache-Control and Surrogate-Key exactly the way Canner's cache proxy expects,
so your pages cache on Canner and purge by tag from your CMS.
Install
npm install @canner-ca/next-cacheRequires Node.js 20 or later. Zero runtime dependencies.
Use it
Call cache() wherever you set response headers in Next.js.
Route Handlers (app/.../route.ts):
import { NextResponse } from 'next/server';
import { cache } from '@canner-ca/next-cache';
export async function GET() {
const posts = await getPosts();
const res = NextResponse.json(posts);
cache(res, { ttl: 3600, tags: ['blog-listing'] });
return res;
}Middleware (middleware.ts) — this is how you cache App Router pages,
since server components can't set response headers themselves:
import { NextResponse } from 'next/server';
import { cache } from '@canner-ca/next-cache';
export function middleware(request) {
const res = NextResponse.next();
if (request.nextUrl.pathname.startsWith('/blog/')) {
// Tag by a path-derived key your CMS webhook can purge.
const slug = request.nextUrl.pathname.split('/')[2];
cache(res, { ttl: 3600, tags: [slug, 'blog-listing'] });
}
return res;
}Pages Router (getServerSideProps / API routes) — pass the Node response:
export async function getServerSideProps({ res, params }) {
const post = await getPost(params.slug);
cache(res, { ttl: 600, tags: [post.id] });
return { props: { post } };
}Full guide (the webhook setup, the dashboard token): https://canner.ca/docs/caching
How App Router caching works
A Next.js App Router URL serves two things: the HTML document (a cold load,
a crawler, a hard navigation — no RSC header) and an RSC payload (client
navigation/prefetch — sends an RSC header). Canner caches only the HTML
document and passes every RSC request straight through to your app. So marking
a page cacheable is safe: client-side navigation is never served a stale or
mismatched response, and the cache speeds up exactly the requests that matter
for SEO and first paint.
You don't configure any of that — it's how Canner's proxy treats Next.js. You just set the headers (via middleware for pages, or directly in route handlers).
API
cache(target, options)
target is a Response/NextResponse, a Node ServerResponse/NextApiResponse,
or a Headers. Returns the same target for chaining.
applyCacheHeaders(headers, options)
Lower-level, when you already hold a Headers instance.
options
| Option | Type | Notes |
|---|---|---|
| ttl | number (required) | Seconds Canner may serve the cached response. Sets s-maxage. Positive integer. |
| tags | string \| number \| Array<string \| number> | Tags for purging. Sets Surrogate-Key. Numbers are coerced; duplicates and whitespace tags are dropped. |
| browserTtl | number | Optional. Seconds the visitor's browser may cache (sets max-age). Omit to keep tag purges instant. |
What Canner caches
The same rules this helper produces:
- method
GET/HEAD, status200, noRSCrequest header Cache-Control: publicwith a positives-maxage(ormax-age)- no
Set-Cookie Varyabsent,Accept-Encoding, or the Next.js router tokens (which Canner handles via the HTML/RSC split above)- body under 8 MB
It only ever adds headers
This package 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 you get a development-only warning instead — your cookie is left
untouched. A bad ttl sets no headers and warns; only passing something that
isn't a response/Headers throws.
Non-goals
- It doesn't configure your CMS webhook — that's two copy-paste values in the Canner dashboard (Settings → Caching).
- It doesn't implement stale-while-revalidate; after a purge the next request re-renders once and re-caches.
For Astro, see @canner-ca/astro-cache.
License
MIT
