Tutorial
Add pagination, saved draft previews, and cache revalidation with the nextRevalidate plugin to a Next.js site.
Add three features used in production to the list and detail pages from Get started.
Pagination
Put Pagination directly inside List to move between pages of limit entries. The default mode="local" replaces the list without changing the URL.
<List api={client.blog} limit={6} orders="-publishedAt" href="/blog/{id}"> <article><h2><ListItem id="title" /></h2></article> <Pagination /> <Error template /> <Null>No published entries yet.</Null></List>To keep the page in the URL, set mode="url" and pass the page's searchParams to List. Back, forward, and reload work as expected. Opening ?page=2 directly renders page two on the server.
import { client } from "@/lib/client";import { List, ListItem, Pagination, Error, Null } from "@genko-me/react/next";
type Props = { searchParams: Promise<Record<string, string | string[] | undefined>>;};
export default function NewsPage({ searchParams }: Props) { return ( <main> <h1>News</h1> <List api={client.blog} limit={10} orders="-publishedAt" href="/blog/{id}" searchParams={searchParams}> <article><h3><ListItem id="title" /></h3></article> <Pagination mode="url" param="page" previousLabel="Previous" nextLabel="Next" /> <Error template /> <Null>No news yet.</Null> </List> </main> );}Pagination renders only <nav aria-label="Pagination">, previous and next buttons, and the current position (1 / 3). It has no default styling; use className for your site's design. The relevant button is disabled on the first or last page. Pagination is hidden when everything fits on one page.
If a page has two URL-based lists, give each one a different param.
Limits. Pagination fetches the next page through an SDK Server Action. It does not work with static export (output: "export") or the Edge runtime. Clients configured with custom fetch or wrapRequest are also unsupported. A signature lasts one hour; reload after it expires or the API key changes.
Preview drafts
If you passed the detail page's searchParams to View, preview works without another Route Handler. The code from Get started already does this.
Set a preview URL template in the dashboard API settings (/w/your-workspace/apis/blog/settings).
https://your-site.example/blog/{id}?draftKey={key}The dashboard replaces {id} and {key}. For local testing, use http://localhost:3000 as the host.
Save an entry and select Preview to see its draft on the same detail page. View then:
- Fetches the draft without caching it, even if the published view has a
cachesetting. - Adds
noindex, nofollowandno-referrermeta tags. - Shows a preview notice and an exit button.
The exit button removes draftKey from the URL and returns to the published view. An invalid key shows Null (or the 404 page if notFound is set), without falling back to the published entry.
Adjust the notice position and style with previewNotice.
<View api={client.blog} id={id} searchParams={searchParams} previewNotice={{ position: "bottom", className: "preview-bar" }}>Use previewNotice={false} to hide it, or previewNotice={{ unstyled: true }} to remove its default styling. A parent's unstyled does not affect the notice.
You can instead add a preview Route Handler that enables Draft Mode before redirecting to the detail page. nextPreview from @genko-me/sdk/next provides a handler that reads id and draftKey and redirects with 307. See Plugins and Next.js.
Revalidate with webhooks
Use a genko.me webhook and the nextRevalidate plugin to refresh cached pages after an entry changes.
First, add the plugin to the client in lib/client.ts.
import "server-only";import { createGenko } from "@genko-me/sdk";import { nextRevalidate } from "@genko-me/sdk/next";import { apis } from "./schema";
export const genko = createGenko({ workspace: "your-workspace", apiKey: process.env.GENKO_API_KEY!, apis, plugins: [ nextRevalidate({ secret: process.env.GENKO_WEBHOOK_SECRET!, revalidate: (event) => { if (event.api !== "blog" || event.workspace !== "your-workspace") return []; if (event.event === "webhook.test") return ["/blog"]; return event.entry ? ["/blog", `/blog/${event.entry.id}`] : ["/blog"]; }, }), ],});export const client = genko.apis;Expose the plugin's POST as a Route Handler.
import { genko } from "@/lib/client";
export const POST = genko.revalidate.POST;In the dashboard API settings, select Add webhook. Set its destination to https://your-site.example/api/revalidate and its secret to the value of GENKO_WEBHOOK_SECRET. When the signature matches, the plugin calls revalidatePath() for each path returned by revalidate and responds with JSON such as {"revalidated":["/blog","/blog/1"]}. A mismatch returns 401. An empty secret causes an error during client initialization.
Revalidation has no effect on uncached pages. List and View fetch on every request by default. It matters only for lists or detail pages with a cache duration such as cache={60}.
<List api={client.blog} limit={6} orders="-publishedAt" cache={60}>For deletion and unpublishing events, revalidate the entry path as well as the list. Do the same when entry.status returns to draft. Manually publishing and saving emits entry.created or entry.updated, not entry.published. See Webhooks for events and delivery limits.
If you rebuild through your hosting provider, point the webhook to its deploy hook instead. You do not need this plugin in that setup.
Next steps
Last updated: October 6, 2026