@plenary/next
v0.5.1
Published
Plenary Next.js integration: webhook revalidation handlers and cache-tagged fetch.
Readme
@plenary/next
Next.js glue for Plenary: turn entry webhooks into cache revalidation, and tag content fetches so dependent pages revalidate themselves.
Webhook revalidation (App Router)
// app/api/revalidate/route.ts
import { createRevalidateHandler } from "@plenary/next";
export const POST = createRevalidateHandler({
secret: process.env.PLENARY_WEBHOOK_SECRET!,
});In the Plenary console, add a webhook on each model pointing at
/api/revalidate, subscribed to the entry events, with the secret in an
x-plenary-secret custom header (marked secret). That's the whole setup: every
event revalidates the entry's own URL, built from its folder path and slug
(/news + hello-world → /news/hello-world).
When a model's URLs don't mirror folder + slug, or other pages show its content, map it:
export const POST = createRevalidateHandler({
secret: process.env.PLENARY_WEBHOOK_SECRET!,
paths: {
"blog-post": ({ entry }) => ["/blog", `/blog/${entry.slug}`],
"global-settings": "/", // fixed path or list also fine
},
});A mapped model replaces the default URL; compose with the exported entryUrl
to keep it. Unmapped slugless entries (singletons) revalidate nothing. The
events option narrows which entry events fire (default: all five —
revalidation is idempotent, so over-firing beats a stale page). The console's
Test button gets a dry run: a 200 reporting the paths it would have
revalidated.
The handler answers 401/405/400 for requests that will never succeed, so bad deliveries fail fast in the delivery log instead of retrying for seven hours.
Cache tags — no path map at all
Path maps drift: add a page that renders a menu, forget to add it to the map, serve stale content. Tags don't. Give the content client a tagged fetch:
import { createClient } from "@plenary/client";
import { createTaggedFetch } from "@plenary/next";
export const plenary = createClient({
baseUrl: process.env.PLENARY_URL!,
siteKey: "main",
apiKey: process.env.PLENARY_API_KEY!,
locale: "en",
fetch: createTaggedFetch(),
});Every content read is now tagged plenary:model:<key> (and
plenary:entry:<slugOrId> for single-entry reads). Enable tags on the handler:
export const POST = createRevalidateHandler({
secret: process.env.PLENARY_WEBHOOK_SECRET!,
tags: true,
});Each event then also revalidates the model and entry tags — every page that fetched that content regenerates, including the ones you forgot you built.
Pages Router
// pages/api/revalidate.ts
import { createPagesRevalidateHandler } from "@plenary/next/pages";
export default createPagesRevalidateHandler({
secret: process.env.PLENARY_WEBHOOK_SECRET!,
});Same options, built on res.revalidate. Cache tags are an App Router feature,
so tags is ignored here.
Authentication
Plenary does not sign webhook requests yet, so authentication is the shared
secret above (constant-time compared; also accepted as a ?secret= query
parameter, names configurable via secretHeader/secretQuery). Payload
parsing and verification live in @plenary/client
(parseWebhookRequest, verifyWebhookSecret, and the payload types) for use
outside Next.js; HMAC verification will land there when signing ships.
