WP Manifestindependent plugin directory
manifest / integrations / nextjs-revalidate

Next.js revalidate

WordPress Plugin to revalidate Next.js pages

by superhuit · github.com/superhuit-agency/nextjs-revalidate · website

0stars
2.7krelease downloads
≈240active sites
1forks

Install

The author publishes release zips, so WP-CLI can install straight from GitHub:

wp plugin install https://github.com/superhuit-agency/nextjs-revalidate/releases/download/v1.6.9/nextjs-revalidate-v1.6.9.zip

Readme

Next.js revalidate

Next.js plugin allows you to purge & re-build the cached pages from the WordPress admin area. It also automatically purges & re-builds when a page/post/... is saved or updated.

The revalidation request is sent to an endpoint composed from the settings — the revalidate domain joined to the revalidate path — with two query arguments.

  1. The relative path to revalidate
  2. The secret to protect the revalidation endpoint.

Settings

Setting Required Default
Revalidate domain yes — (e.g. https://example.com)
Revalidate path no /api/revalidate
FSE revalidate path no /api/revalidate-fse
Revalidate secret yes
Revalidate on FSE update no on for a new install, off for an upgrade

A standard install fills in the domain and the secret. The paths exist for apps that route these endpoints somewhere else; each falls back to the default shown in its placeholder when left empty.

FSE templates

Next.js renders its pages inside WordPress FSE templates, and holds the whole template structure as one cached value. Saving a template or a template part in the site editor, resetting one to its theme default, or switching themes therefore changes every page at once — so the plugin sends one request to the FSE endpoint, with the secret and no path, and the front-end's pages rebuild lazily from there. Nothing is enqueued and nothing is rebuilt page by page.

https://example.com/api/revalidate-fse?secret=my-super-secret-string

Menu changes do not trigger this: menu items are fetched at request time by the front-end and are not part of the template snapshot.

Revalidate on FSE update starts on for a new install and off for a site upgrading from an earlier release — an existing front-end may not serve that endpoint yet, and every template save would otherwise ask it for a route it does not have. Switch it on once the front-end is serving it.

Sites upgrading from 1.6.x had a single, fully-qualified revalidate URL. It is split into a domain and a path automatically on the first admin request after the upgrade, custom paths and all — nothing to do by hand.

Example

https://example.com/api/revalidate?path=/hello-world/&secret=my-super-secret-string

Based on the Next.js On-demand revalidation documentation

Probing the front-end

The Probe tab of the settings screen asks the front-end to rebuild one path straight away, and shows what it answered — including the error message and its code when it did not work. It is a real revalidation and not a dry run: the page is rebuilt exactly as it would be after an edit, using the saved settings, so it answers "does this site revalidate right now" rather than "would these values work".

A probe is never counted towards the "not keeping this site up to date" warning: pressing it can neither raise that warning nor clear it. It is written to the log file when logging is on, marked 🔎 Probe.

Requirements

  • Requires PHP 7.4+
  • Requires WordPress 5.0+

API functions

Neither function can tell you whether the front-end has rebuilt anything. Each answers what the plugin took on: nextjs_revalidate_purge_url whether the revalidation was accepted into the queue, and nextjs_revalidate_schedule_purge_url whether the schedule was registered — which is one step further away, since that revalidation is only enqueued when the date time passes, and can be refused then. The queue is drained afterwards, by cron, so there is no return value in this plugin that could report the outcome of a delivery that has not happened yet. Delivery is at most once — a revalidation that is attempted and fails is written to the log and dropped, never retried.

nextjs_revalidate_purge_url

Enqueues a revalidation of any URL, to be delivered to the front-end by the next run of the queue's cron.

Usage

nextjs_revalidate_purge_url( $url );

Arguments

Name Type Description
url string The URL to purge
priority int Optional. Lower numbers are purged earlier; equal priorities keep insertion order. Default 10.

Returns

bool — whether the revalidation was accepted into the queue. It is false when the site is unconfigured, which is a refusal: the revalidate domain or the secret is missing, nothing has been queued, and nothing will be. It is also false if the queue insert failed. A URL already waiting in the queue is accepted (true) without being queued twice.

It is never a statement about the front-end. A true says the plugin will try.

nextjs_revalidate_schedule_purge_url

Registers a revalidation of the given URL for a future date time. Nothing is enqueued until then, so a schedule registered on a configured site is still refused at its due time if the site is unconfigured by then.

Usage

nextjs_revalidate_schedule_purge_url( $datetime, $url );

Arguments

Name Type Description
datetime string The date time when to purge
url string The URL to purge

Returns

bool — whether this call registered the scheduled purge. It is false when that URL is already registered for that date time: the schedule stands, and this call added nothing to it. It is also false if the write failed.

Which posts are revalidated

A post is revalidated when the front-end could hold a page for it: its post type is viewable — WordPress's own publicly_queryable test, via is_post_type_viewable() — and its status is publish or private, or it has just left publish for draft or trash. Posts of a post type that is not viewable are never revalidated, whatever their status.

A headless site registering post types with publicly_queryable => false while its front-end still renders their permalinks can say so with the filter below.

Integrations

An integration is a third-party plugin whose changes this plugin reacts to when that plugin is present. It is never a dependency: with the plugin absent nothing registers, and no feature here requires one.

Redirection

A headless front-end resolves a redirect inside the cached page of the path it redirects from, so creating, editing, deleting, enabling or disabling a redirect in Redirection leaves that path answering as it did before until its cache entry expires. With Redirection active, this plugin enqueues a revalidation of the source path whenever a redirect changes, so the redirect starts — or stops — working within the time the queue takes to drain.

Only a redirect the front-end could resolve for a single path produces a revalidation: its source is a literal path rather than a regular expression, and it is enabled. A regular expression source matches an unbounded set of paths, so there is no single path to rebuild; it is skipped, and the skip is written to the log when logging is switched on. Source paths are reduced to their path component, dropping any query string or domain the source was stored with, and given the site's trailing slash convention, so they match the form post permalinks are enqueued in.

Changing a redirect's source revalidates the path it stopped redirecting as well as the new one, on Redirection versions whose update action carries the redirect's previous state. Redirection 5.9.0 passes the redirect's id instead, where only the new source path is revalidated.

Filters

nextjs_revalidate_should_revalidate_redirect

Filters whether a redirect's source path is revalidated. Return false to leave the path alone — the escape hatch for a site whose front-end resolves redirects some other way.

Usage

add_filter( 'nextjs_revalidate_should_revalidate_redirect', function( $should_revalidate, $path, $redirect ) {
    if ( 0 === strpos( $path, '/legacy/' ) ) return false;
    return $should_revalidate;
}, 10, 3 );

Arguments

Name Type Description
should_revalidate bool Whether the source path is revalidated
path string The source path, normalised
redirect object The redirect the path is the source of, as Redirection's own Red_Item

nextjs_revalidate_purge_should_revalidate_post_on_save

Filters whether the given post is revalidated. Applied last, so it can admit a post the rules above decline, or decline one they admit.

Usage

add_filter( 'nextjs_revalidate_purge_should_revalidate_post_on_save', function( $should_revalidate, $post_id ) {
    if ( 'my-headless-type' === get_post_type( $post_id ) ) return 'publish' === get_post_status( $post_id );
    return $should_revalidate;
}, 10, 2 );

Arguments

Name Type Description
should_revalidate bool Whether the post is revalidated
post_id int The post ID

nextjs_revalidate_purge_action_permalink

Filters the permalink added to the purge queue by the "Purge cache" row and bulk actions. Return false to keep it out of the queue.

Arguments

Name Type Description
permalink string|false The post permalink. False if the post is not revalidatable
post_id int The post ID

Tests

Two commands, and which one a test belongs to depends on whether it needs real WordPress state. See docs/adr/0008-two-testing-idioms.md. A third idiom is not a command at all — see the runbook below.

npm run test:php — standalone scripts

Scripts under tests/ that stub the handful of WordPress functions their subject touches. No framework, no database, no Docker; they run anywhere PHP does, including a sandbox with neither.

npm run test:integration — the integration suite

PHPUnit tests under tests/integration/ that boot WordPress with this plugin active and assert on the revalidation queue: given some WordPress state and an event, which paths does the queue revalidate, in what order, at what priority?

From a fresh checkout:

npm install
composer install
npm run test:integration

wp-env installs the Redirection plugin alongside this one, in both environments, so the redirect integration can be exercised without assembling an install by hand. The suite's bootstrap loads it and creates its tables when it is there, and skips the tests that need it when it is not.

The command starts wp-env itself — wp-env start is idempotent, so running it again costs seconds. It runs with --no-scripts and against the tests environment, so the development site keeps its database and its settings; wp-env does bring the development containers up alongside the tests ones, which means port 8080 has to be free. Docker must be running. The first run downloads WordPress and its PHPUnit test library and takes a few minutes.

Write a test by extending NextJsRevalidate\Tests\QueueTestCase, which configures the site, enqueues paths and reads the queue back:

$this->configure_site();                       // a configured site — an
                                               // unconfigured one refuses
$this->enqueue( '/hello-world/', 5 );          // enqueue a path at a priority
$this->assertQueueRevalidates( [ '/hello-world/' ] );  // paths, in drain order
$this->assertQueueRevalidatesAtPriorities( [ '/hello-world/' => 5 ] );
$this->assertQueueHolds( [ home_url( '/hello-world/' ) ] ); // the permalinks

The queue holds permalinks, and those permalinks revalidate paths — the two are kept apart because on a network they can disagree. assertQueueHolds() takes permalinks; assertQueueRevalidates() takes paths and normalises against the site's home url, so a test survives a change of testsPort.

The queue table is created once in the bootstrap and emptied around every test. It has to be: RevalidateQueue::add_item() runs its own transaction, whose COMMIT also commits the one WP_UnitTestCase uses to roll a test back.

The manual test runbook — checks no command can run

An admin notice rendering on the screen it is meant for, a redirect saved through Redirection's own UI, a site upgraded from 1.6.9. What puts a check here is reach — whether the answer can only come from a browser, a console or a file on a running site — never its subject. See docs/adr/0012-a-third-testing-idiom.md.

Two files, partitioning the checks between them. No step appears in both.

  • docs/manual-tests.md — the core pass: steps on a single site. Run it before every release, and after any change worth ten minutes.
  • docs/manual-tests-extended.md — the extended pass: everything else, including the network stack and a site upgraded from the previous release. Run the part covering what you touched, and all of it before a structural release.

Both are committed unchecked. Tick the boxes in your working copy during a pass; never commit the ticks.

Read the full README on GitHub →

Releases

TagPublishedAssetDownloads
v1.6.9 Apr 28, 2026 nextjs-revalidate-v1.6.9.zip 1
v1.6.8 Feb 5, 2026 nextjs-revalidate-v1.6.8.zip 235
v1.6.6 Oct 20, 2025 nextjs-revalidate-v1.6.6.zip 5
v1.6.5 Jul 5, 2024 nextjs-revalidate-v1.6.5.zip 514
v1.6.4 Apr 25, 2024 nextjs-revalidate-v1.6.4.zip 944
v1.6.3 Feb 14, 2024 nextjs-revalidate.zip 39
v1.6.2 Feb 6, 2024 nextjs-revalidate.zip 3
v1.6.1 Feb 5, 2024 nextjs-revalidate.zip 2
v1.6.0 Jan 26, 2024 nextjs-revalidate.zip 2
v1.5.0 Jan 9, 2024 nextjs-revalidate.zip 4
v1.4.1 Nov 23, 2023 nextjs-revalidate.zip 5
v1.4.0 Nov 15, 2023 nextjs-revalidate.zip 4
v1.3.0 Sep 12, 2023 nextjs-revalidate.zip 110
v1.2.2 May 23, 2023 nextjs-revalidate.zip 170
v1.2.1 May 17, 2023 nextjs-revalidate.zip 105
v1.2.0 May 15, 2023 nextjs-revalidate.zip 4
v1.1.3 Apr 27, 2023 nextjs-revalidate.zip 4
v1.1.2 Apr 27, 2023 nextjs-revalidate.zip 4
v1.1.1 Apr 20, 2023 nextjs-revalidate.zip 4
v1.1.0 Feb 24, 2023 nextjs-revalidate.zip 122
v1.0.2 Feb 17, 2023 nextjs-revalidate.zip 4
v1.0.1 Dec 27, 2022 nextjs-revalidate.zip 396
v1.0.0 Dec 26, 2022 nextjs-revalidate.zip 9

Active-site estimate ≈240 comes from the median of recent superseded releases. Method.