WordPress on-demand cache revalidation for Next.js
Update Next.js content in real time when WordPress posts change using webhook-driven cache revalidation
This tutorial shows you how to keep a Next.js site on Pantheon in sync with a WordPress CMS using on-demand, tag-based cache revalidation. When you publish or update a post in WordPress, a webhook fires and tells Next.js exactly which cached data to refresh — no full rebuild required.
Learning objectives
This tutorial walks you through:
- Setting up
@pantheon-systems/nextjs-cache-handlerin a Next.js project - Tagging cached WordPress data with surrogate keys in Next.js
- Creating a revalidation API endpoint in Next.js that accepts webhook requests
- Adding a WordPress mu-plugin that sends webhooks when content changes
- Configuring shared secrets on both sites via Terminus Secrets Manager
Requirements
- A Next.js 16 site on Pantheon (Quick start)
- A WordPress site on Pantheon
- A GitHub account with SSH configured
- Install the following:
- Git
- Terminus*
- Terminus Secrets Manager Plugin (built into Terminus 4.2.0+; earlier versions require the plugin)
* Requires logging in after installation.
Quick start with test upstreams
If you want to skip the manual setup and get a working site immediately, create a Pantheon site from one of the test upstreams that have WordPress cache invalidation pre-configured:
| Upstream | Next.js Version | Cache Strategy |
|---|---|---|
nextjs_15_cache_starter | 15 | ISR + Surrogate-Key headers in next.config.mjs |
nextjs_16_cache_starter | 16 | 'use cache' + cacheTag() — adapter exposes tags to internal router |
These are not core upstreams and are not shown by default in terminus upstream:list. Use the --all flag to find them:
These upstreams include the @pantheon-systems/nextjs-cache-handler package, WordPress REST API integration, surrogate key tagging, and a secured revalidation endpoint. To get end-to-end cache invalidation working:
- Create a site from the upstream.
- Set
WORDPRESS_API_URLto your WordPress site's REST API base URL. - Set a shared
WEBHOOK_SECRETon both the Next.js and WordPress sites. - Install the WordPress mu-plugin on your WordPress site.
- Install the Pantheon Advanced Page Cache plugin on WordPress so that WordPress' CDN caches are cleared appropriately.
The rest of this tutorial walks through each piece in detail if you want to understand or customize the setup.
How it works
The revalidation flow has three parts:
-
Next.js fetches and tags data. When your Next.js pages request WordPress content, the responses are cached and tagged with surrogate keys like
post-list,post-{id},post-{slug}, andterm-{id}. -
WordPress sends a webhook. When you publish, update, or delete a post in WordPress, a mu-plugin extracts the relevant surrogate keys and sends them to your Next.js revalidation endpoint.
-
Next.js revalidates matching cache entries. The endpoint calls
revalidateTag()for each received key, so only the affected pages are refreshed on the next request.
Both sides use the same surrogate key pattern, which enables targeted invalidation instead of a full cache purge.
Set up the cache handler
The @pantheon-systems/nextjs-cache-handler package provides persistent caching across Pantheon's horizontally scaled containers. Install it and configure both the cache handler and the Next.js 16 'use cache' directive.
Install the package
Create the cache handler
Create cache-handler.mjs in the root of your project. These files use .mjs rather than .ts because cache handlers are loaded by Next.js at runtime outside the TypeScript compilation pipeline — Node.js executes them directly as ES modules:
Create the use cache handler
Create cacheHandlers/remote-handler.mjs in your project. This handler supports the Next.js 16 'use cache' directive:
Configure next.config.mjs
Update your next.config.mjs to register both cache handlers and enable the 'use cache' directive:
Tag cached data with surrogate keys
Your Next.js application needs to tag cached WordPress data with surrogate keys so the revalidation endpoint knows which cache entries to invalidate. This example uses a lib/wordpressService.ts file that wraps WordPress REST API calls.
Your page components call these functions directly:
Create the revalidation endpoint
Create app/api/revalidate/route.ts in your Next.js project. This endpoint receives webhook requests from WordPress and calls revalidateTag() for each surrogate key:
The endpoint validates the shared secret from either the X-Webhook-Secret header or the request body, then iterates over the surrogate_keys array and revalidates each tag. In Next.js 16, revalidateTag() accepts an optional second argument specifying revalidation behavior — passing { expire: 0 } forces immediate expiration so the next request fetches fresh content rather than serving a stale cached response. This is the recommended pattern for webhook-triggered revalidation.
Add the WordPress mu-plugin
Create wp-content/mu-plugins/nextjs-webhook.php on your WordPress site. This plugin sends a webhook to your Next.js revalidation endpoint whenever a post is published, updated, or deleted.
Configure secrets
Both sites need shared secrets to authenticate the webhook. Set them using Terminus Secrets Manager.
Next.js site
Set the WordPress API URL and the webhook secret that the revalidation endpoint uses to validate incoming requests. Replace my-nextjs-site with your site name:
WordPress site
Set the revalidation endpoint URL and the shared webhook secret. Replace my-wp-site with your WordPress site name:
The WEBHOOK_SECRET on the Next.js site and NEXTJS_WEBHOOK_SECRET on the WordPress site must be the same value. Generate a strong random string for this secret.
Trigger a build
After setting secrets, push a commit to your Next.js repository to trigger a new build so the environment variables take effect.
Test the integration
With both sites configured, verify that content changes in WordPress automatically update your Next.js site:
-
Open your Next.js site's
/blogspage in a browser and note the cached timestamp. -
In WordPress, publish a new post or edit an existing one.
-
Refresh the
/blogspage on your Next.js site. The page should reflect the updated content with a new cached timestamp. -
Navigate to
/blogs/{slug}for the post you changed and confirm the content is up to date.
If the content does not update, check the following:
- Verify that the secrets are set correctly on both sites using
terminus secret:site:list. - Review the Next.js application logs for revalidation messages using
terminus node:logs:runtime:get <site>.<env>(built into Terminus 4.2.0+; earlier versions require the terminus-node-logs-plugin). - Confirm the mu-plugin file is in
wp-content/mu-plugins/and is loaded by WordPress.
Conclusion
You now have a WordPress site that automatically keeps your Next.js pages up to date through targeted cache revalidation. When content changes in WordPress, only the affected cache entries are invalidated — the rest of your site continues serving cached responses.
To continue building your Next.js site on Pantheon: