@datum-cloud/strapi-revalidate

Framework-agnostic runtime cache invalidation for Strapi v5 in SSR frontends. Receives Strapi publish webhooks, clears affected cache tags, next request serves fresh data.
2,396
Install
npm install @datum-cloud/strapi-revalidate
Latest Version:0.3.0
License:MIT
Last Updated:Oct 23, 2026
Links: GitHub  ·  npm
Maintainer: scotwells

@datum-cloud/strapi-revalidate

Keep your SSR frontend in sync with Strapi content — automatically.

npm version License: MIT Node

strapi-revalidate is a framework-agnostic Node.js library for SSR frontends using Strapi v5 as a headless CMS. It caches Strapi content locally (file, memory, or Redis), receives Strapi publish webhooks, and invalidates only the affected cache entries — so the next request always serves fresh data without a full rebuild or manual intervention. Battle-tested in production at datum.net.

How this differs from Strapi-side cache plugins

strapi-revalidate solves a different problem than strapi-cache, strapi-plugin-rest-cache, and the dozens of similar Strapi plugins on npm and GitHub.

Those are Strapi plugins — they install into config/plugins.js, run inside the Strapi server process, and cache Strapi's own API responses before they leave the server. They have no awareness of your frontend at all.

strapi-revalidate lives in your frontend SSR process — Astro, Next.js, SvelteKit, Nuxt, Express, whatever. It caches the data your frontend fetches from Strapi, and invalidates that cache the moment Strapi publishes new content via webhook.

The two approaches are complementary. If you're already running strapi-cache on your Strapi instance, keep it — strapi-revalidate works alongside it without conflict.

Install

npm install @datum-cloud/strapi-revalidate

Quickstart

Each snippet below is a complete, copy-pasteable example. Pick your framework.

Astro

// src/lib/strapi.ts
import { createStrapiRevalidate } from '@datum-cloud/strapi-revalidate';

export const { client, cache, webhook } = createStrapiRevalidate({
  url: import.meta.env.STRAPI_URL,
  token: import.meta.env.STRAPI_TOKEN,
  webhook: { secret: import.meta.env.STRAPI_WEBHOOK_SECRET },
});
---
// src/pages/blog/index.astro
import { fetchArticles } from '@datum-cloud/strapi-revalidate/content';
import { client, cache } from '../../lib/strapi';

const articles = await fetchArticles({ client, cache });
---
<ul>
  {articles.map((a) => <li><a href={`/blog/${a.slug}`}>{a.title}</a></li>)}
</ul>
// src/pages/api/strapi-webhook.ts
import type { APIRoute } from 'astro';
import { webhook } from '../../lib/strapi';

export const POST: APIRoute = async ({ request }) => {
  let status = 200;
  let body: unknown = {};
  await webhook(request, {
    status: (code) => {
      status = code;
    },
    json: (value) => {
      body = value;
    },
  });
  return new Response(JSON.stringify(body), {
    status,
    headers: { 'Content-Type': 'application/json' },
  });
};

Next.js (App Router)

// lib/strapi.ts
import { createStrapiRevalidate } from '@datum-cloud/strapi-revalidate';

export const { client, cache, webhook } = createStrapiRevalidate({
  url: process.env.STRAPI_URL!,
  token: process.env.STRAPI_TOKEN!,
  webhook: { secret: process.env.STRAPI_WEBHOOK_SECRET },
});
// app/blog/page.tsx
import { fetchArticles } from '@datum-cloud/strapi-revalidate/content';
import { client, cache } from '@/lib/strapi';

export default async function BlogPage() {
  const articles = await fetchArticles({ client, cache });
  return (
    <ul>
      {articles.map((a) => (
        <li key={a.documentId}>
          <a href={`/blog/${a.slug}`}>{a.title}</a>
        </li>
      ))}
    </ul>
  );
}
// app/api/strapi-webhook/route.ts
import { NextResponse } from 'next/server';
import { webhook } from '@/lib/strapi';

export async function POST(request: Request) {
  let status = 200;
  let body: unknown = {};
  await webhook(request, {
    status: (code) => {
      status = code;
    },
    json: (value) => {
      body = value;
    },
  });
  return NextResponse.json(body, { status });
}

SvelteKit

// src/lib/strapi.ts
import { createStrapiRevalidate } from '@datum-cloud/strapi-revalidate';
import { env } from '$env/dynamic/private';

export const { client, cache, webhook } = createStrapiRevalidate({
  url: env.STRAPI_URL,
  token: env.STRAPI_TOKEN,
  webhook: { secret: env.STRAPI_WEBHOOK_SECRET },
});
// src/routes/blog/+page.server.ts
import { fetchArticles } from '@datum-cloud/strapi-revalidate/content';
import { client, cache } from '$lib/strapi';

export const load = async () => {
  const articles = await fetchArticles({ client, cache });
  return { articles };
};
// src/routes/api/strapi-webhook/+server.ts
import { json } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
import { webhook } from '$lib/strapi';

export const POST: RequestHandler = async ({ request }) => {
  let status = 200;
  let body: unknown = {};
  await webhook(request, {
    status: (code) => {
      status = code;
    },
    json: (value) => {
      body = value;
    },
  });
  return json(body, { status });
};

Environment variables

Variable Required Default Description
STRAPI_URL Yes — Your Strapi Cloud base URL
STRAPI_TOKEN Yes — Strapi API token (Settings → API Tokens)
STRAPI_WEBHOOK_SECRET Recommended — Secret set in Strapi webhook config
STRAPI_CACHE_TTL No 3600 Cache TTL in seconds
STRAPI_TIMEOUT No 3 Request timeout in seconds

Strapi webhook setup

  1. In your Strapi Cloud admin, open Settings → Webhooks → Add new webhook.
  2. Name: strapi-revalidate (or whatever you like).
  3. URL: the public URL of the API route you mounted in the quickstart, e.g. https://example.com/api/strapi-webhook.
  4. Headers: add Authorization: Bearer <STRAPI_WEBHOOK_SECRET> so the handler can verify the source. The handler also accepts X-Strapi-Signature or strapi-webhook-secret headers if you'd rather not use Authorization.
  5. Events: enable at minimum entry.publish, entry.unpublish, and entry.update. Add entry.delete if you want deletions to clear cache too.
  6. Save the webhook. Publish any entry — within seconds, the matching cache tags are cleared and the next request serves fresh data.

Cache drivers

Driver Default Use case
file ✓ Single-instance servers. Survives process restarts.
memory Dev and testing. Fast, lost on restart.
redis Multi-instance / edge deploys. Requires ioredis. Coming v1.x.

Select a driver via cache.driver in config:

createStrapiRevalidate({
  url: process.env.STRAPI_URL!,
  token: process.env.STRAPI_TOKEN!,
  cache: { driver: 'memory' },
});

How the fallback cache works

Every successful Strapi fetch writes to two caches: the primary TTL cache (configurable expiry, cleared by webhook) and a persistent fallback cache (no expiry, file-backed). If Strapi is unreachable on a future fetch, the fallback serves stale data instead of throwing or returning empty — keeping your site online during Strapi outages or deploys. The fallback is on by default and cannot be disabled.

Contributing

Issues and PRs welcome at github.com/datum-cloud/strapi-revalidate. This library was extracted from production code at datum.net; contributions that keep the public surface small and the dependencies few are especially appreciated.

Releasing

This repo uses Changesets + GitHub Actions for automated releases. When your PR introduces a user-visible change, add a changeset:

npx changeset

Pick the bump type (patch / minor / major) and write a short summary. Commit the generated .changeset/*.md alongside your changes.

After your PR merges to main, the Release workflow opens (or updates) a chore: version packages PR that consumes the pending changesets, bumps package.json, and updates CHANGELOG.md. Merging that PR triggers an automated npm publish via npm OIDC trusted publishing — no long-lived npm token in repo secrets, and each release ships with a verifiable provenance attestation tied back to the source commit.

License

MIT

Related Packages

create-strapi-launchpad

CLI to scaffold the official Strapi LaunchPad demo application with the frontend...

368
layero-detection

Layero's unified framework / build-command / output / runtime detection — one de...

5,271
routeplay

Regression-test SSR routes across server HTML, cold loads, and real client-side...

961 0
@authaction/server-sdk

AuthAction server-side SDK — session-based auth for Next.js, Nuxt, Remix, and Sv...

734
esa-pcg-cli

CLI for building and deploying Next.js, Astro and Nuxt apps onto pages-cache-gat...

5,595

Version History

Version Released License
0.3.0 MIT
0.2.0 MIT
0.1.2 MIT
0.1.1 MIT
0.1.0 MIT