Tutorial
Add pagination and draft previews with genko-pagination in plain HTML, then choose how to reflect content updates.
Add the features needed to operate the list and detail pages from Get started.
Pagination
Put genko-pagination directly under genko-list and pass loadPage a function that takes a page number and returns that page.
<genko-list id="posts" href="/blog/{id}"> <article><h2><genko-list-item field="title"></genko-list-item></h2></article> <genko-pagination mode="url" param="page"></genko-pagination> <genko-loading template>Loading…</genko-loading> <genko-error template></genko-error> <genko-null>No entries yet.</genko-null></genko-list>list.loadPage = async (page) => { try { const result = await genko.apis.blog.list({ limit: 6, offset: (page - 1) * 6, orders: '-publishedAt' }); return { ok: true, result }; } catch { return { ok: false, message: 'Could not fetch entries.' }; }};By default, with no mode, only the list changes; the URL stays the same. mode="url" syncs with ?page= and browser Back navigation. If a page has several lists, give each a distinct param. genko-pagination displays nothing when there is one page or fewer.
This setup has no server, so loadPage calls @genko-me/sdk directly from the browser. It cannot hide an API key on the server as the Next.js Pagination component can. If the key must stay private, use a framework with a server, such as Astro or Next.js.
Preview drafts
Read draftKey from the URL and pass it to the fetch method. A matching key returns the draft; otherwise the API returns 404 and getOrNull() returns null.
const params = new URLSearchParams(location.search);const id = params.get('id');const draftKey = params.get('draftKey') ?? undefined;
genko.apis.blog .getOrNull(id, draftKey ? { draftKey } : undefined) .then((entry) => { detail.removeAttribute('state'); if (!entry) detail.setAttribute('state', 'empty'); detail.entry = entry ?? undefined; }) .catch(() => detail.setAttribute('state', 'error'));Use <genko-preview-notice> for the preview banner. This element always renders a “Previewing draft” banner and an “Exit” button, so your code must show it only when draftKey is present.
<genko-view id="detail"> <genko-preview-notice id="notice" hidden></genko-preview-notice> <h1><genko-view-item field="title"></genko-view-item></h1> <genko-view-item field="body"></genko-view-item> <genko-loading template></genko-loading> <genko-error template></genko-error> <genko-null>Entry not found.</genko-null></genko-view>document.getElementById('notice').hidden = !draftKey;
if (draftKey) { const meta = document.createElement('meta'); meta.name = 'robots'; meta.content = 'noindex, nofollow'; document.head.appendChild(meta);}The “Exit” button already removes draftKey from the URL and reloads with location.replace() inside genko-preview-notice. Your code does not need to do this.
To keep a preview out of search indexes in this setup, add <meta name="robots"> yourself. There is no server to vary response headers or SSR tags, so add the tag to document.head only during preview. Set the preview URL template in the dashboard's API settings to match this page.
https://your-site.example/post.html?id={id}&draftKey={key}Rebuild or revalidate with webhooks
This setup has no server to receive a webhook directly. The list and detail pages query the delivery API whenever they open, so publishing or updating an entry appears on the next view without a rebuild or revalidation.
Only if you cache delivery API responses in a CDN or reverse proxy do you need revalidation. In that case, set up a small separate server that can receive webhooks, such as a Node.js API route or Cloudflare Worker. Use verifyGenkoSignature from @genko-me/sdk to check the signature. The receiver is the same as in the JavaScript tutorial.
See Webhooks for event types, payloads, and delivery limits.
Next steps
Last updated: October 6, 2026