Automate CI/CD for Strapi 5 + Next.js: Rebuild on Content Publish with GitHub Actions
Learn step‑by‑step how to configure Strapi 5 webhooks, a Next.js on‑demand ISR endpoint, and GitHub Actions so every published entry triggers a fresh build and deployment.
By the Revisionary team · · 4 min read
When a content editor publishes an entry in Strapi 5, the site should show the change instantly without manual redeploys. You can achieve this by wiring Strapi webhooks to a CI/CD pipeline (GitHub Actions) that triggers a Next.js build and deployment. The following guide shows the full setup: Strapi webhook configuration, a secure on‑demand revalidation API in Next.js, and a GitHub Actions workflow that runs on webhook calls.
Overview of the workflow
- Strapi webhook fires on the
entry.publishevent. - The webhook calls a protected API route (
/api/revalidate) in the Next.js app. - The API route invokes
revalidatePath(orrevalidateTag) to purge the ISR cache. - The route also triggers a GitHub Actions workflow via the
repository_dispatchevent. - GitHub Actions checks out the repo, runs
npm install && npm run build && npm run start(or deploys to Vercel/Netlify), completing the CI/CD cycle.
The whole process runs in the Node.js runtime, because Edge runtimes cannot perform on‑demand ISR.
1. Configure the Strapi webhook
Create a webhook in Strapi’s admin panel (Settings → Webhooks) or add it programmatically in config/server.ts.
// ./config/server.ts
import { defineConfig } from '@strapi/types';
export default defineConfig({
// Global webhook settings (optional)
webhook: {
// Add an Authorization header that will be checked by Next.js
headers: {
Authorization: 'Bearer MY_STRAPI_WEBHOOK_SECRET',
},
// Verify payload signatures (recommended)
signature: {
secret: process.env.STRAPI_WEBHOOK_SECRET,
},
},
// Register the publish webhook
webhook: {
enabled: true,
events: ['entry.publish'],
url: `${process.env.NEXT_PUBLIC_SITE_URL}/api/revalidate`,
// Only fire for content types that need ISR
contentTypes: ['api::article.article', 'api::page.page'],
},
});Security considerations
- Verify the
Authorizationheader. - Validate the HMAC signature if you enable
signaturein Strapi (see the webhook signature verification example in the Strapi docs【3†L112-L136】). - Restrict the webhook URL to POST requests only.
2. Create a protected revalidation endpoint in Next.js
Place the file under app/api/revalidate/route.ts (App Router) and mark it as a Node.js route by adding export const runtime = 'nodejs'; – this avoids the Edge runtime limitation【8†L16-L17】.
// app/api/revalidate/route.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export const runtime = 'nodejs'; // Ensure Node.js runtime for ISR
/**
* Verify the secret sent by Strapi.
*/
function verifySecret(req: NextRequest): boolean {
const auth = req.headers.get('authorization');
return auth === `Bearer ${process.env.NEXT_WEBHOOK_SECRET}`;
}
export async function POST(req: NextRequest) {
if (!verifySecret(req)) {
return new NextResponse('Invalid secret', { status: 401 });
}
const payload = await req.json();
const { model, uid, entry } = payload;
// Determine the path(s) that need revalidation.
// Example: article slugs are stored as entry.slug
const paths = [] as string[];
if (model === 'api::article.article') {
paths.push(`/articles/${entry.slug}`);
} else if (model === 'api::page.page') {
paths.push(`/${entry.slug}`);
}
try {
// Revalidate each path on-demand.
for (const path of paths) {
await revalidatePath(path);
}
// Trigger a GitHub Actions run via repository_dispatch.
await triggerGitHubDispatch();
return NextResponse.json({ revalidated: true, paths });
} catch (err) {
console.error('Revalidation error:', err);
return new NextResponse('Revalidation failed', { status: 500 });
}
}
/**
* Call the GitHub repository_dispatch endpoint.
*/
async function triggerGitHubDispatch() {
const token = process.env.GITHUB_TOKEN; // personal access token with repo scope
const repo = process.env.GITHUB_REPO; // e.g. "owner/repo"
const response = await fetch(`https://api.github.com/repos/${repo}/dispatches`, {
method: 'POST',
headers: {
Accept: 'application/vnd.github+json',
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
event_type: 'strapi-publish',
client_payload: { source: 'strapi' },
}),
});
if (!response.ok) {
throw new Error('GitHub dispatch failed');
}
}revalidatePathis the on‑demand ISR helper from Next.js【6†L226-L233】.- The route runs only in Node.js, satisfying the runtime requirement.
- The
triggerGitHubDispatchfunction sends arepository_dispatchevent that GitHub Actions can listen for.
3. Set up the GitHub Actions workflow
Create .github/workflows/strapi-publish.yml in your repo.
name: Rebuild on Strapi Publish
on:
repository_dispatch:
types: [strapi-publish]
jobs:
build-and-deploy:
runs-on: ubuntu-latest
permissions:
contents: read
pages: write # if deploying to GitHub Pages
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install dependencies
run: npm ci
- name: Build Next.js app
run: npm run build
- name: Deploy to Vercel (or Netlify, etc.)
env:
VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
run: |
npm install -g vercel
vercel --prod --token $VERCEL_TOKENHow it works
- When Strapi publishes content, the webhook calls
/api/revalidate. - The API route validates the request, revalidates ISR paths, then posts a
repository_dispatchevent to GitHub. - GitHub Actions picks up the
strapi-publishevent, runs the build, and deploys the fresh static output.
4. Environment variables
| Variable | Where it lives | Purpose |
|---|---|---|
STRAPI_WEBHOOK_SECRET | Strapi .env | HMAC secret for webhook signing |
NEXT_WEBHOOK_SECRET | Next.js .env.local | Same secret used to verify the request |
GITHUB_TOKEN | GitHub Actions secret GITHUB_TOKEN | Personal access token for repository_dispatch |
GITHUB_REPO | GitHub Actions secret GITHUB_REPO | owner/repo identifier |
VERCEL_TOKEN, VERCEL_ORG_ID, VERCEL_PROJECT_ID | GitHub Actions secrets | Vercel deployment credentials |
5. Testing the integration locally
- Run Strapi:
npm run develop(requires Node 18‑24 and a DB as per the requirements page)【/docs/getting-started/requirements】. - Run Next.js in Node mode:
npm run devand setNEXT_PUBLIC_SITE_URL=http://localhost:3000. - Use a tool like
curlto POST a fake webhook payload:bash curl -X POST http://localhost:3000/api/revalidate \ -H "Content-Type: application/json" \ -H "Authorization: Bearer MY_STRAPI_WEBHOOK_SECRET" \ -d '{"model":"api::article.article","uid":"article","entry":{"slug":"hello-world"}}' - Verify that the console logs
revalidatedand that the GitHub dispatch request succeeds (check the Actions tab).
6. Optional: Tag‑based revalidation for list pages
If you have a page that lists many articles (/articles), you can tag the fetch call and revalidate the tag instead of individual paths.
// app/articles/page.tsx
export const fetchCache = 'force-no-store'; // optional for fresh data
export const revalidate = 60; // fallback ISR
export default async function ArticlesPage() {
const res = await fetch(`${process.env.STRAPI_API_URL}/articles`, {
next: { tags: ['articles'] },
});
const data = await res.json();
// render list …
}In the webhook handler, replace revalidatePath with:
await revalidateTag('articles');This invalidates every page that used the articles tag, saving you from looping over many paths【7†L262-L268】.
7. Deploying the Next.js app
- Vercel – the default for Next.js. The
vercelCLI command in the workflow pushes the latest build. - Netlify – use the Netlify CLI (
netlify deploy --prod) instead of Vercel commands. - Self‑hosted – if you run Next.js on a VPS, replace the deployment step with a SSH script that pulls the repo, runs
npm ci && npm run build && pm2 restart next.
8. Observability and monitoring
- Strapi’s audit log records each
entry.publishaction, giving you an immutable trail of who triggered a rebuild【12†L12-L13】. - Next.js automatically adds
x‑nextjs‑cacheresponse headers (HIT,STALE,MISS,REVALIDATED) to help you debug ISR behaviour【9†L23-L25】. - In GitHub Actions, enable the
--debugflag for the Vercel CLI to capture deployment logs.
9. Common pitfalls
| Symptom | Cause | Fix |
|---|---|---|
401 Invalid secret from /api/revalidate | Mismatched secret between Strapi and Next.js | Ensure both STRAPI_WEBHOOK_SECRET and NEXT_WEBHOOK_SECRET are identical and loaded from env files. |
| ISR does not run on Edge runtime | Using Edge instead of Node.js runtime | Add export const runtime = 'nodejs'; to the route file. |
| GitHub dispatch fails with 404 | Wrong owner/repo or missing token scopes | Verify GITHUB_REPO and grant repo scope to the PAT. |
| Build times are long | Uncached node_modules in CI | Add a caching step: actions/cache@v4 for ~/.npm or node_modules. |
10. Extending the pipeline
- Content versioning – If you use the Revisionary plugin, you can also listen to
entry.unpublishandentry.deleteevents to clean up stale pages. - Preview builds – Combine the webhook with Vercel preview deployments to generate a draft URL for editors.
- Feature flags – Pass a
previewflag in the payload and have the Next.js route decide whether to revalidate the live site or a preview deployment.
By wiring Strapi 5 webhooks to a protected Next.js on‑demand ISR endpoint and a GitHub Actions workflow, you achieve a fully automated CI/CD loop: every publish instantly invalidates the static cache and triggers a fresh build. The approach respects security best practices, works with TypeScript, and can be extended with Revisionary’s audit log for full traceability.
Further reading
- Strapi webhook documentation – https://docs.strapi.io/cms/backend-customization/webhooks
- Next.js Incremental Static Regeneration – https://nextjs.org/docs/app/guides/incremental-static-regeneration
- GitHub Actions
repository_dispatch– https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#repository_dispatch
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.