Kwisple
Browse the docs

Content and data

Caching

Where delivery answers are cached, which tags they carry, what publishing purges, the signed revalidation webhook to your site, Cache legen, scheduled purges and patterns for Next.js and Astro.

Published content is served from caches and purged by tag when something changes; nothing is rebuilt unless your site is a static build. Two layers cache content for a custom-code site: the platform caches delivery API answers, and your site caches the SDK's answers. A publish empties both, the platform's directly and yours through a signed webhook. This page describes both layers, what each change purges, and how to fit your own caching in.

The two layers

LayerWhatKey and tagsLifetimePurged by
Platform: delivery APIevery read answer of /api/v1/*, per site and querytag cms:site:<siteId> (brand answers also brand:<workspaceId>)at most 1 hourpublish, trash, restore, scheduled moments, schema push, seed, Cache legen
Your site, Next.jsevery SDK GET in Next's data cachekwisple:all + a per-method tagrevalidate, default 3600 sthe webhook → createRevalidateRoute
Your site, Astro (on demand)delivery answers in the server's memorythe request URLcacheTtl, default 3600 s; at most 2000 answersthe webhook → the injected /api/revalidate
Your site, static buildthe built HTMLnoneuntil the next builda rebuild (deploy hook or Opnieuw bouwen)

Reads made with the development model (kwisple dev, x-kwisple-schema: dev) are never cached on the platform. Preview reads (x-kwisple-preview) are never cached anywhere.

HTTP headers of the delivery API

Delivery answers are bearer-authenticated, so they are never marked cacheable for shared caches. Cached reads answer cache-control: private, max-age=60, stale-while-revalidate=600 with vary: authorization, x-kwisple-preview; preview reads and writes answer private, no-store. A CDN in front of the platform never serves one token's answer to another caller.

Cache tags

Platform tags (cms:*)

TagCovers
cms:site:<siteId>every delivery read of the site
cms:section:<siteId>:<handle>reads that list a section
cms:entry:<entryId>one entry
cms:uri:<siteId>:<locale>:<uri>the "which entry lives at this URI" lookup
cms:globals:<siteId>global sets
cms:redirects:<siteId>redirects
cms:schema:<siteId>the content model

These tags are also sent to your site in the webhook, so you can tag your own cached work with them (see Patterns for Next.js).

SDK tags (kwisple:*)

Every SDK GET that is not a preview read carries kwisple:all (SITE_TAG, exported from @kwisple/sdk). Some methods add a second tag:

MethodExtra tagNotes
site()kwisple:sitealso memoised per client instance
brand()kwisple:brand
privacy()kwisple:privacy
matchRedirect()kwisple:redirectscached for 300 s, misses included
remoteSchema(), exportSeed(), formSlots(), stagingInfo(), verifyPreview()nonenever cached

The revalidate routes always purge kwisple:all, so every SDK answer goes on every webhook, whatever tags the webhook carries. The extra tags are for your own revalidateTag calls.

What publishing purges

When an editor clicks Zet online (publish), the dashboard runs afterContentChange (apps/dashboard/src/lib/services/cms-publish.ts) after the content is saved:

  1. Tags. publishEntry (in @kwisple/content) returns the entry's tag, its section's tag and the tags of every entry that relates to it (from the relation index), so a listing or a page that shows this entry is purged too. cms:site:<siteId> is always added.
  2. Platform purge. For a custom-code site every tag expires hard, before the webhook goes out, so your site's refetch gets fresh answers. For hosted sites the broad tags are refreshed in the background and only the edited entry's tags expire hard; its public paths are re-warmed.
  3. Webhook. A custom-code site with a webhook URL gets the signed webhook with the same tags and event: "cms.published".
  4. Consent services. Newly embedded videos, maps or pixels are added to the site's consent configuration in the background.
  5. Rebuild. For a site whose hosting is set to Opnieuw bouwen, a debounced rebuild is queued (at most one per minute).
  6. Scheduled purges. A future Online vanaf or Offline vanaf gets a purge at that moment (see Scheduled moments).

A failure in any step never fails the publish: the editor sees De site kon niet verwittigd worden: <reason>. De inhoud is wel opgeslagen. and the content is live on the platform.

Other changes and their events

Changeevent in the webhookTags
Publish an entrycms.publishedentry, section, related entries, cms:site
Move an entry in a structurecms.movedentry, section, cms:site
Move to trash / restorecms.trashed / cms.restoredentry, section, related entries, cms:site
Save a global setcms.global.savedglobals, cms:site
Save or delete a redirectcms.redirect.saved / cms.redirect.deletedredirects, cms:site
An Online vanaf / Offline vanaf moment passescms.scheduledthe entry's tags
Cache legencache.purgekwisple:all, every cms:* site tag, every entry tag (at most 200)
Brand kit savedbrand.updatedkwisple:brand
Privacy or consent settings savedprivacy.updatedkwisple:privacy, kwisple:all

The brand webhook goes to every custom-code site of the client, staging included. A kwisple push purges the platform's cache for the site but sends no webhook: the deployment that follows the push in CI, or Cache legen, refreshes your site.

The revalidation webhook

Set the URL under Website → Instellingen → Ontwikkeling in Webhook bij publiceren (webhook on publish), normally https://<your site>/api/revalidate. Saving it creates the Webhook-geheim (webhook secret) when the site has none; put that in KWISPLE_WEBHOOK_SECRET. kwisple link writes it to .env for local work.

Request

POST /api/revalidate
content-type: application/json
x-kwisple-signature: 5d1c…e9 (hex)

{
  "tags": ["cms:entry:ent_…", "cms:section:site_…:news", "cms:site:site_…"],
  "event": "cms.published",
  "siteId": "site_…",
  "at": "2026-10-30T09:12:44.120Z"
}
PartMeaning
x-kwisple-signatureHMAC-SHA256 of the raw body with the site's webhook secret, hex encoded
tagscache tags that changed; the SDK routes read at most 200
eventwhat happened (table above)
siteIdthe site the webhook is for
atwhen it was sent, ISO 8601

Delivery rules

  • One attempt with a 5 s timeout, no retries. The result goes back to the editor.
  • Redirects are never followed: a URL that redirects fails with Webhook-URL stuurt door; gebruik de eindbestemming.
  • In production the URL must be https and resolve to a public address. During local development http and localhost work.
  • Any 2xx answer counts as delivered.

The SDK routes

Next.js, one file:

// src/app/api/revalidate/route.ts
import { createRevalidateRoute } from "@kwisple/sdk/next";

export const POST = createRevalidateRoute({ secret: process.env.KWISPLE_WEBHOOK_SECRET });

It verifies the signature, then calls revalidateTag(tag, { expire: 0 }) for kwisple:all and every tag in the body, and answers { revalidated: true, tags }.

Astro: the kwisple() integration injects /api/revalidate when the site renders on demand. It verifies the signature and empties the whole memory cache (clearKwispleCache()), answering { revalidated: true, cleared, tags }.

Both answer 401 with Ongeldige handtekening. when the signature does not match and 400 with Body is geen JSON. when the body is not JSON. To verify by hand in another framework, use verifySignature(secret, rawBody, signature) from @kwisple/sdk/next; compute it over the raw body, before parsing.

Cache legen

Cache legen (clear cache) under Website → Instellingen → Algemeen is for agency staff (the site.advanced right). It asks Zeker? Cache legen before it runs, then:

  1. expires every platform tag of the site at once: delivery answers, host lookups, privacy, brand, every section, entry and URI (the agency's shared footer-credit tag is only marked stale);
  2. sends the webhook with event: "cache.purge" to a custom-code site;
  3. reports Cache geleegd: with platform and website marked gelukt or mislukt, or website (webhook mislukt) with the reason. Without a webhook URL the reason is Geen webhook ingesteld.

Use it after an infrastructure change (for example a new storage address that cached pages still point at) or when a site shows something old and you want to rule caching out.

The platform-wide purge

Platform administrators have Alles legen (empty everything) in Platformbeheer. It queues the cache.purgeAll job, which runs the same purge and webhook for every live site of every live client, one after the other, and logs the counts. A second click within 30 seconds gets Er loopt al een opdracht om alles te legen.

Scheduled moments

A published page is cached until something purges it, so it would not appear or disappear on its own at Online vanaf or Offline vanaf. When an entry is published with a future moment, the dashboard queues a cms.scheduled job for that moment plus five seconds (so the date has really passed when your site refetches). The job purges the entry's tags on the platform and sends the webhook with event: "cms.scheduled", exactly like a publish. Jobs are deduplicated per entry and moment; a moment that is changed later simply purges once more. A site that went to the trash in the meantime is skipped.

Patterns for Next.js

Every SDK read is already cached and purged; most sites need nothing more. The starter's catch-all route renders on first request and is then served from the cache:

// src/app/[[...path]]/page.tsx
export const dynamicParams = true;

export function generateStaticParams(): { path?: string[] }[] {
  return [];
}

Keep request APIs (headers(), cookies()) out of cached routes, or every page renders per request again. Draft mode (live preview) bypasses the cache by itself.

When you cache your own work (a computed menu, a third-party call that combines with content), tag it so the webhook reaches it:

import { SITE_TAG } from "@kwisple/sdk";
import { unstable_cache } from "next/cache";
import { client } from "@/lib/cms";

export const getMenu = unstable_cache(
  async () => {
    const tree = await client.tree("pages", { maxLevel: 2 });
    return tree.map((n) => ({ label: n.title, href: n.url, children: n.children.length }));
  },
  ["menu"],
  { tags: [SITE_TAG] },
);

For finer control, tag with the platform tags from the webhook (cms:entry:<id>, cms:section:<siteId>:<handle>); createRevalidateRoute purges every tag it receives.

The client's revalidate option sets the time-based fallback (default 3600 s). Lower it only if your site cannot receive the webhook.

Patterns for Astro

  • On demand (recommended). output: "server" with an adapter. The integration wraps the client's fetch in the memory cache (GET answers with status 200 or 404, no preview reads), injects /api/revalidate, and turns the cache off in astro dev. Tune it with kwisple({ cacheTtl: 600 }). Call clearKwispleCache() yourself if you need to.
  • One process. The memory cache lives in one server process. When you run several instances behind a load balancer, the webhook empties only the instance that receives it; the others serve old answers until cacheTtl passes. Run one instance or lower cacheTtl.
  • Static. A static build has no route to purge. Under Hosting & deploys set Als inhoud verandert to Opnieuw bouwen (rebuild on every publish, at most once a minute), or point Webhook bij publiceren at your host's deploy hook.

Proxies and CDNs in front of your site

The webhook purges caches the platform and the SDK control. It cannot purge a CDN or proxy that caches your HTML. The Cloudflare proxy (orange cloud) caches HTML for a while and keeps serving the old page after a publish, so content no longer appears within seconds. Either set the site's DNS records to DNS only (grey cloud), or configure the CDN not to keep HTML longer than about a minute. Caching static files (/_next/static, fonts, images) is fine.

Preview pages must never be cached by a CDN: they answer private, no-store (the Astro middleware sets this for previewed responses).

Checklist

  • /api/revalidate is deployed and Webhook bij publiceren points at it (final URL, https).
  • KWISPLE_WEBHOOK_SECRET equals Webhook-geheim; after Vernieuwen (renew), redeploy.
  • Your own caches carry kwisple:all or a platform tag.
  • Static sites rebuild on publish.
  • No CDN keeps your HTML for long.
  • After a publish, the dashboard shows no De site kon niet verwittigd worden message.

Where to go next

Last updated 2026-10-30