npm.io
0.2.0 • Published 3d ago

@canner-ca/next-cache

Licence
MIT
Version
0.2.0
Deps
0
Size
19 kB
Vulns
0
Weekly
0

@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-cache

Requires 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.
swr number Optional. Stale-while-revalidate window in seconds — Canner serves the stale copy instantly past ttl while refreshing once in the background. Sets stale-while-revalidate.
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.

Draft mode

Preview unpublished content on the real URL without purging the published page. Set the bypass cookie from a route you've authorized — it pairs with (doesn't replace) Next's own draftMode():

// app/api/draft/route.ts
import { cookies } from 'next/headers';
import { enableDraft } from '@canner-ca/next-cache';

export async function GET(request: Request) {
  const secret = new URL(request.url).searchParams.get('secret');
  if (secret !== process.env.PREVIEW_SECRET) return new Response('no', { status: 401 });
  enableDraft(await cookies(), process.env.CANNER_BYPASS_SECRET!); // secret: Caching tab
  return Response.redirect(new URL('/', request.url));
}

enableDraft accepts the cookies() store, a NextResponse, or a Node ServerResponse (Pages Router). disableDraft(target) turns it off; isDraft(request) tells you whether to fetch draft content. You can also bypass with a ?__canner_bypass=<secret> link or an X-Canner-Bypass: <secret> header.

What Canner caches

The same rules this helper produces:

  • method GET/HEAD, status 200, no RSC request header
  • Cache-Control: public with a positive s-maxage (or max-age)
  • no Set-Cookie
  • Vary absent, 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).

For Astro (including a native cache provider), see @canner-ca/astro-cache.

License

MIT

Keywords