Docs

Next.js helpers

@capacms/sdk/nextjs: cache tags, webhook revalidation, draft mode and graphql() in a server component.

View as Markdown
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).