Next.js helpers
@capacms/sdk/nextjs: cache tags, webhook revalidation, draft mode and graphql() in a server component.
import { createClient } from "@capacms/sdk/next";
import { withCache, tagsFor } from "@capacms/sdk/nextjs";
const fetchWithCache = withCache(fetch, {
tags: tagsFor({ model: articleModelId }),
revalidate: 60,
});
const capa = createClient({ baseUrl, apiKey, version: "2026-10-01", fetch: fetchWithCache });withCache merges { next: { tags, revalidate } } into every fetch call.
tagsFor builds Capa surrogate keys: m:, e:, k:, and t:, by id, and
tagsFor({ namespace: "articles" }) builds capa:model:articles, the tag
for a GraphQL read (see GraphQL), and capa:media. revalidateFromWebhook
revalidates all of them for the entry and model a webhook names, and for a
media event, the file's f: key and capa:media.
Webhook revalidation pairs with the existing signature verifier:
// app/api/capa/route.ts
import { revalidateTag } from "next/cache";
import { revalidateFromWebhook } from "@capacms/sdk/nextjs";
import { verifyWebhookSignature } from "@capacms/sdk";
export async function POST(request: Request) {
const raw = await request.text();
const ok = await verifyWebhookSignature({
payload: raw,
header: request.headers.get("capa-signature") ?? "",
secret: process.env.CAPA_WEBHOOK_SECRET!,
});
if (!ok) return new Response("bad signature", { status: 400 });
await revalidateFromWebhook({
payload: JSON.parse(raw),
revalidateTag,
});
return new Response("ok");
}draftClient is server-only. Pass Next's draft state in from the caller so the
SDK never imports next/*:
import { draftMode } from "next/headers";
import { draftClient } from "@capacms/sdk/nextjs";
import type { CapaQuery } from "./capa-graphql"; // written by capa-codegen --graphql
const capa = await draftClient<CapaQuery>({
production,
draft,
isDraft: async () => (await draftMode()).isEnabled,
});CapaQuery types the GraphQL builder on the client it returns, as it does
for createClient<CapaQuery>; getCapaClient<CapaQuery> and
getPublishedClient<CapaQuery> take it the same way. Without it each helper
returns an untyped client. getCapaClient sends its GraphQL reads as it
sends its REST reads, which Next does not keep, unless a call gives tags or
revalidate; then it keeps them in Next's data cache as graphql() does
(see The typed builder).