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. 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.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.