@datum-cloud/strapi-revalidate
| Install | |
|---|---|
npm install @datum-cloud/strapi-revalidate |
|
| Latest Version: | 0.3.0 |
| License: | MIT |
| Last Updated: | Oct 23, 2026 |
| Links: | GitHub · npm |
@datum-cloud/strapi-revalidate
Keep your SSR frontend in sync with Strapi content — automatically.
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
- In your Strapi Cloud admin, open Settings → Webhooks → Add new webhook.
- Name:
strapi-revalidate(or whatever you like). - URL: the public URL of the API route you mounted in the quickstart, e.g.
https://example.com/api/strapi-webhook. - Headers: add
Authorization: Bearer <STRAPI_WEBHOOK_SECRET>so the handler can verify the source. The handler also acceptsX-Strapi-Signatureorstrapi-webhook-secretheaders if you'd rather not useAuthorization. - Events: enable at minimum
entry.publish,entry.unpublish, andentry.update. Addentry.deleteif you want deletions to clear cache too. - 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
CLI to scaffold the official Strapi LaunchPad demo application with the frontend...
Layero's unified framework / build-command / output / runtime detection — one de...
AuthAction server-side SDK — session-based auth for Next.js, Nuxt, Remix, and Sv...
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 |