Skip to main content

Caching

Delivery responses are designed to live behind a CDN. Krios sets standard cache headers + surrogate keys; invalidation is automatic on publish / unpublish.

Headers

Cache-Control: public, max-age=60, stale-while-revalidate=3600
Surrogate-Key: entry:{id} type:{apiName} site:{slug} project:{slug}
Vercel-Cache-Tag: entry:{id},type:{apiName},site:{slug},project:{slug}

max-age=60 keeps fresh responses for 60 seconds at the edge. stale-while-revalidate=3600 lets the edge serve stale content for an hour while it refreshes in the background — fast page loads even after the TTL.

The same tags are emitted in two headers: Vercel-Cache-Tag (comma-delimited) is what Vercel's CDN purges by, and Surrogate-Key (space-delimited) is the equivalent for Fastly / Cloudflare Enterprise. (Vercel ignores Surrogate-Key; other CDNs ignore Vercel-Cache-Tag — emitting both keeps the response portable.)

Preview responses

Preview-mode responses (preview-key auth or ?preview=1) override the cache headers:

Cache-Control: private, no-store

Drafts must never enter the shared cache.

Invalidation

When an entry is published, unpublished, or deleted, Krios purges the affected tags inline (not via a deferred job — the purge is a fast tag-invalidation call, so there's no reason to lag it). The tags purged:

  • entry:{id} — every cached representation of this entry
  • type:{apiName} — every list query that included this type
  • site:{slug} — every per-site response (rare; used for site-level config changes)
  • project:{slug} — every response for the project (used after schema changes)

On Vercel, the purge calls invalidateByTag from @vercel/functions, which marks the matching Vercel-Cache-Tag entries stale (next request revalidates in the background — no user-facing latency). A purge failure is logged, not fatal: content still self-heals at max-age, so a failed purge never blocks the publish. Off Vercel, the purge logs the tags only — swap in another CDN's tag-purge in lib/cache's purgeCacheTags for a non-Vercel host.

Surrogate keys in your frontend cache

If you cache delivery responses in your own frontend (Next.js's unstable_cache, Redis, etc.), reuse the surrogate keys as cache tags:

import { unstable_cache } from "next/cache";

const fetchEntry = unstable_cache(
async (id: string) => await krios.getEntry(id),
["krios-entry"],
{
tags: [`entry:${id}`, `type:articlePage`],
revalidate: 60,
},
);

Set up a webhook from Krios that calls revalidateTag(...) on your frontend when entries change.

Webhooks

Krios fires HMAC-signed webhooks on every publish / unpublish / save. Subscribe at Settings → Webhooks to wire frontend cache invalidation:

// Your /api/krios-webhook handler
import { revalidateTag } from "next/cache";
import { verifyKriosSignature, parseKriosWebhook } from "@krios/sdk";

export async function POST(req: Request) {
const body = await req.text();
// verifyKriosSignature is async; { toleranceSeconds } adds opt-in replay protection.
const ok = await verifyKriosSignature(
req.headers.get("x-krios-signature"),
body,
process.env.WEBHOOK_SECRET!,
{ toleranceSeconds: 300 },
);
if (!ok) return new Response("bad signature", { status: 401 });
const event = parseKriosWebhook(body);
if (event.event === "entry.published" || event.event === "entry.unpublished") {
revalidateTag(`entry:${event.data.entryId}`);
revalidateTag(`type:${event.data.contentTypeApiName}`);
}
return new Response("ok");
}

Cache TTL tuning

The defaults (max-age=60, stale-while-revalidate=3600) work for most projects. For static-content sites where publishes are rare, bump max-age to 300 or 600 to reduce origin load — the surrogate-key invalidation still purges instantly on publish.