Strapi 5 webhook to invalidate Vercel CDN cache tags for Next.js pages
Learn step‑by‑step how to configure a Strapi 5 webhook that triggers Vercel’s tag‑based CDN cache invalidation, using Next.js cache tags and the Vercel edge‑cache API.
By the Revisionary team · · 4 min read
A Strapi 5 webhook can call Vercel’s edge‑cache API to purge cache tags whenever content is created, updated, or published, keeping your Next.js pages fresh without a full redeploy. Below is a complete guide that covers webhook configuration, secure handling, Next.js tag assignment, and Vercel tag invalidation.
Overview of the workflow
- Assign cache tags in Next.js – each page that depends on a Strapi entry adds a
Vercel-Cache-Tag(viacacheTag). - Configure a Strapi webhook – listen to
entry.updateandentry.publishevents for the relevant content types. - Create a secure Next.js API route – receives the webhook payload, verifies it, and calls Vercel’s
edge‑cache/invalidate‑by‑tagsendpoint. - Optionally use Revisionary – if you need per‑version tags, Revisionary can provide the entry version ID to build unique tags.
The result is on‑demand revalidation: when an editor updates an article in Strapi, the associated Next.js page’s cache tag is invalidated and the next request fetches fresh data.
1. Tagging Next.js pages with cacheTag
Next.js 15 (and earlier 13/14) provides the cacheTag function to attach one or more tags to a cached response. The tag is sent as the Vercel-Cache-Tag response header, which Vercel’s CDN stores.
// app/articles/[slug]/page.tsx
import { cacheTag } from 'next/cache';
import { fetchArticle } from '@/lib/api';
export default async function ArticlePage({ params }: { params: { slug: string } }) {
'use server';
// Tag the response with the article ID – useful for invalidation
cacheTag(`article-${params.slug}`);
const article = await fetchArticle(params.slug);
return (
<article>
<h1>{article.title}</h1>
<div dangerouslySetInnerHTML={{ __html: article.body }} />
</article>
);
}When the page renders, Next.js adds Vercel-Cache-Tag: article‑my‑post to the response. Any request that hits Vercel’s CDN will store the response under that tag.
1.1 Multiple tags
If a page combines several content sources (e.g., author profile + article), you can add multiple tags:
cacheTag(['article-123', 'author-45']);2. Setting up the Strapi 5 webhook
Strapi 5 lets you define global webhook defaults in ./config/server.js (or server.ts). Define a bearer token for authentication and point the webhook to a Next.js API route.
// config/server.js
module.exports = {
webhooks: {
defaultHeaders: {
Authorization: `Bearer ${process.env.WEBHOOK_TOKEN}`,
},
// Optional: verify payload signatures with X‑Webhook‑Signature
},
};2.1 Create the webhook in the admin UI
- Settings → Webhooks → Create new webhook
- URL:
https://my-next-app.vercel.app/api/strapi-revalidate - Events: select
Entry.updateandEntry.publishfor the content types you want to invalidate (e.g., Article). You can also addEntry.deleteif you want to purge removed pages. - Headers: the
Authorizationheader is added automatically fromdefaultHeaders. - Save.
Strapi will now POST a JSON payload to the specified URL whenever the selected events occur.
3. Secure Next.js webhook handler
Create an API route (or a Route Handler in the App Router) that verifies the request and calls Vercel’s edge‑cache API.
// src/app/api/strapi-revalidate/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { Vercel } from '@vercel/sdk';
export async function POST(req: NextRequest) {
// 1️⃣ Verify bearer token
const auth = req.headers.get('authorization');
if (auth !== `Bearer ${process.env.WEBHOOK_TOKEN}`) {
return new NextResponse('Unauthorized', { status: 401 });
}
// 2️⃣ Parse Strapi payload
const payload = await req.json();
const { event, entry } = payload;
// We only care about updates and publishes
if (!['entry.update', 'entry.publish'].includes(event)) {
return new NextResponse('Ignored event', { status: 200 });
}
// 3️⃣ Build the cache tag – match the tag used in Next.js
// Assuming the entry ID is the slug or a numeric ID
const tag = `article-${entry.id}`; // adjust if you use slug
// 4️⃣ Call Vercel edge‑cache invalidate‑by‑tags API
const vercel = new Vercel({
bearerToken: process.env.VERCEL_BEARER_TOKEN,
});
try {
await vercel.edgeCache.invalidateByTags({
projectIdOrName: process.env.VERCEL_PROJECT,
tags: [tag],
});
return new NextResponse('Cache invalidated', { status: 200 });
} catch (err) {
console.error('Vercel invalidate error', err);
return new NextResponse('Failed to invalidate', { status: 500 });
}
}Key security points
- Bearer token – matches the token set in Strapi’s
defaultHeaders. - Optional HMAC verification – Strapi can send an
X‑Webhook‑Signatureheader; verify it with a shared secret for extra protection. - Limit allowed events – the handler explicitly ignores unrelated events.
3.1 Environment variables
Add the following to your Vercel project’s environment:
WEBHOOK_TOKEN– shared secret for Strapi ↔ Next.js.VERCEL_BEARER_TOKEN– token generated in Vercel’s dashboard (Settings → Tokens).VERCEL_PROJECT– your Vercel project name or ID.
4. Using Revisionary for version‑aware tags (optional)
If you version content with the Revisionary plugin, you may want to invalidate the cache only when a specific version is published. Revisionary stores each version in the audit log and provides a content API that returns version metadata.
// Example: fetch the latest published version ID
const res = await fetch(`${process.env.STRAPI_URL}/api/articles/${entry.id}?populate=revision`,
{ headers: { Authorization: `Bearer ${process.env.STRAPI_ADMIN_TOKEN}` } }
);
const data = await res.json();
const versionId = data.data.attributes.revision?.latest?.id;
const tag = `article-${entry.id}-v${versionId}`;Then use the same tag in your Next.js page:
cacheTag(`article-${slug}-v${process.env.NEXT_PUBLIC_VERSION_ID}`);This approach lets you keep older cached versions available while the new version is being reviewed, and only purge the tag when the revision is finally published.
5. Testing the end‑to‑end flow
- Deploy the Next.js app – make sure the
/api/strapi-revalidateroute is reachable. - Create a test article in Strapi.
- Open the article page in a browser; inspect the response headers (DevTools → Network) – you should see
Vercel-Cache-Tag: article-<id>. - Update the article in Strapi’s admin UI.
- Check the webhook logs (
/admin/settings/webhooks→ “History”) – a successful 200 response indicates the handler ran. - Refresh the page – the CDN should serve a fresh response (you’ll see a new
x-vercel-cacheheader value or aMISS).
If the cache tag is not being invalidated, verify:
- The
Authorizationheader matches. - The Vercel token has the
Edge Cache: Invalidatescope. - The tag string exactly matches the one sent by Next.js (case‑sensitive).
6. Common pitfalls and troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| 401 Unauthorized from webhook handler | Token mismatch | Ensure WEBHOOK_TOKEN is identical in Strapi config and Vercel env. |
No Vercel-Cache-Tag header in response | cacheTag not called before any async fetch | Place cacheTag at the top of the server component/function. |
Cache not purged, still HIT after update | Tag typo or missing in invalidate request | Log the tag variable before calling Vercel SDK; compare with header value. |
Vercel SDK throws Invalid project | Wrong projectIdOrName | Use the exact project slug as shown in Vercel dashboard or the numeric project ID. |
Webhook payload missing entry.id | Webhook configured for a collection type without id exposure | In Strapi webhook settings, enable “Include full entry data”. |
7. Full example repository structure
my-next-app/
├─ app/
│ ├─ api/
│ │ └─ strapi-revalidate/
│ │ └─ route.ts ← webhook handler
│ └─ articles/[slug]/
│ └─ page.tsx ← cacheTag usage
├─ .env.local
│ ├─ WEBHOOK_TOKEN=…
│ ├─ VERCEL_BEARER_TOKEN=…
│ └─ VERCEL_PROJECT=my-next-app
└─ package.jsonDeploy with vercel or via the Vercel dashboard; the environment variables are automatically injected.
8. When to prefer Vercel tag invalidation over ISR revalidation
| Use case | Vercel tag invalidation | Next.js revalidateTag |
|---|---|---|
| Immediate purge after any content change | ✅ Immediate, works for any cache‑able response | ❌ Only works from within the same deployment |
| Invalidation across multiple deployments (preview → production) | ✅ Tags are shared across deployments | ❌ Tags are scoped to a single deployment |
| Granular control per content version | ✅ Custom tags (article‑123‑v5) | ✅ Same, but requires extra code |
| Simplicity – no extra SDK | ❌ Requires Vercel SDK or raw fetch | ✅ Built‑in function |
If you already use Vercel’s edge‑cache for static assets or have many preview deployments, tag‑based invalidation is the most robust choice.
9. Summary
- Assign cache tags in Next.js with
cacheTag. - Configure a Strapi 5 webhook that fires on
entry.updateandentry.publish. - Build a secure API route in Next.js that verifies the bearer token and calls Vercel’s
edge‑cache/invalidate‑by‑tagsendpoint. - Optionally enrich tags with Revisionary version IDs for fine‑grained control.
- Test the flow, watch for common pitfalls, and enjoy instant cache freshness without full redeploys.
By wiring Strapi and Vercel together, you get the performance of static generation with the editorial agility of a headless CMS.
Further reading
- Strapi webhook reference – https://docs.strapi.io/cms/backend-customization/webhooks
- Vercel edge‑cache API – https://vercel.com/docs/rest-api/edge-cache/invalidate-by-tag
- Next.js cache tag docs – https://nextjs.org/docs/app/api-reference/functions/cacheTag
- Revisionary documentation – <./docs/getting-started/requirements>
Happy coding!
Sources
The Revisionary team
Developers who build Revisionary, a version history plugin for Strapi 5. We write about Strapi, web development, AI and application security.
This article was written with the help of AI.