Cache Next.js by tag, in one line.
@canner-ca/next-cache sets the Cache-Control and Surrogate-Key headers Canner needs — correctly, every time — in any Next.js place you can set response headers: Route Handlers, Middleware, and getServerSideProps. Canner is App Router aware, so content pages cache for cold loads and crawlers while client navigations always hit your app. It also ships draft-mode helpers to preview unpublished content on the real URL. Zero dependencies.
Use it
Route Handlers
// app/api/posts/route.ts
import { NextResponse } from 'next/server';
import { cache } from '@canner-ca/next-cache';
export async function GET() {
const res = NextResponse.json(await getPosts());
cache(res, { ttl: 3600, tags: ['blog-listing'] });
return res;
}Middleware (App Router pages)
Server components can't set response headers, so you cache App Router pages from middleware. Tag by a path-derived key your CMS webhook can purge.
// middleware.ts
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/')) {
const slug = request.nextUrl.pathname.split('/')[2];
cache(res, { ttl: 3600, tags: [slug, 'blog-listing'] });
}
return res;
}Pages Router
In getServerSideProps or an API route, pass the Node response object.
export async function getServerSideProps({ res, params }) {
const post = await getPost(params.slug);
cache(res, { ttl: 600, tags: [post.id] });
return { props: { post } };
}How App Router caching works
A page 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. Marking a page cacheable is therefore safe: client-side navigation is never stale or mismatched, and the cache speeds up exactly the requests that matter for SEO and first paint. You don't configure any of this — it's how Canner treats Next.js.
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.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 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. Full caching guide.
Source and issues: tools/next-cache/ in the canner repo on GitHub. Licensed MIT. For Astro, see @canner-ca/astro-cache.