Sheraz AhmadSenior Web Developer
Let's talk

Next.js On-Demand Revalidation WordPress: Step-by-Step

Trigger instant content updates on your headless Next.js App Router site using WordPress save_post hooks, secure webhooks, and tag-based cache invalidation.

Quick answer

  • Use Tag-Based Revalidation: In Next.js, fetch data with next: { tags: ['post-123', 'blog-archive'] } instead of path-based revalidation.
  • Secure the Endpoint: Use a shared secret token in the Authorization header between WordPress and Next.js.
  • Hook into WordPress: Use the transition_post_status hook in WordPress to detect when content is published, updated, or deleted.
  • Send the Webhook: POST a payload containing the updated post ID, post type, and tags to your Next.js API route.
  • Invalidate in Next.js: Call revalidateTag() inside a Next.js App Router route handler to instantly clear the cache.

---

Why path-based revalidation fails in production

Many developers start with path-based revalidation (revalidatePath) because it looks simple. However, in a real-world headless WordPress setup, path-based revalidation quickly breaks down.

If you update a blog post, you do not just need to update /blog/my-post. You also need to update the /blog archive page, the category archives, the RSS feed, the search results page, and potentially the homepage featured section. Tracking down every single URL affected by a single post save is a maintenance nightmare.

Tag-based revalidation solves this. By tagging your Next.js fetch requests with granular identifiers (like post-123, post-type-post, or taxonomy-category-5), you can invalidate dozens of pages with a single API call. When a post changes, you tell Next.js to purge all caches associated with those specific tags. Next.js automatically rebuilds those pages on the next request.

Gotcha: Do not use the generic save_post hook without checking the post status. The save_post hook fires multiple times during a single save operation (including revisions and auto-saves), which will flood your Next.js server with redundant revalidation requests.

---

Step 1: Configure Next.js fetch requests with tags

To use tag-based revalidation, you must assign tags when fetching data from the WordPress REST API or GraphQL endpoint in your Next.js application.

Here is how to structure your API fetch helper in the Next.js App Router. This example fetches a single post and tags it with both the specific post ID and the general post type.

// lib/wordpress.ts
export async function getPostBySlug(slug: string) {
  const res = await fetch(`https://api.example.com/wp-json/wp/v2/posts?slug=${slug}`, {
    next: {
      tags: [`post-type-post`, `slug-${slug}`],
    },
    headers: {
      'Content-Type': 'application/json',
    },
  });

  if (!res.ok) {
    throw new Error('Failed to fetch post');
  }

  const posts = await res.json();
  return posts[0] || null;
}

When Next.js renders this page, it registers these tags in its data cache. Now, if you tell Next.js to revalidate post-type-post, every page that fetched data using that tag will have its cache cleared.

---

Step 2: Create the Next.js revalidation route handler

Next, create a secure API route handler in your Next.js App Router to receive the webhook from WordPress. This route validates the request using a shared secret token and executes the revalidation.

Create a file at app/api/revalidate/route.ts:

// app/api/revalidate/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { revalidateTag } from 'next/cache';

export async function POST(request: NextRequest) {
  const authHeader = request.headers.get('authorization');
  const secret = process.env.REVALIDATION_SECRET;

  if (!secret || authHeader !== `Bearer ${secret}`) {
    return NextResponse.json({ message: 'Unauthorized' }, { status: 401 });
  }

  try {
    const body = await request.json();
    const { tags } = body;

    if (!tags || !Array.isArray(tags)) {
      return NextResponse.json({ message: 'Missing tags array' }, { status: 400 });
    }

    tags.forEach((tag) => {
      revalidateTag(tag);
    });

    return NextResponse.json({ revalidated: true, tags, now: Date.now() });
  } catch (err) {
    return NextResponse.json({ message: 'Error revalidating' }, { status: 500 });
  }
}

Add your REVALIDATION_SECRET to your .env.local file. Generate a long, random string for this secret.

---

Step 3: Write the WordPress webhook trigger

To trigger the nextjs on-demand revalidation wordpress setup, you need to write a custom WordPress function that hooks into post status changes and sends a POST request to your Next.js API route.

Add this code to your custom plugin or your active theme's functions.php file. It uses transition_post_status to target only actual content changes, avoiding draft saves and revisions.

<?php
/**
 * Trigger Next.js on-demand revalidation when content changes.
 */
add_action('transition_post_status', 'wp_nextjs_trigger_revalidation', 10, 3);

function wp_nextjs_trigger_revalidation($new_status, $old_status, $post) {
    // Ignore auto-saves and revisions
    if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
        return;
    }

    if ($post->post_type === 'revision') {
        return;
    }

    // Only trigger if the post is published, or was published and is now deleted/drafted
    $is_published = $new_status === 'publish';
    $was_published = $old_status === 'publish';

    if (!$is_published && !$was_published) {
        return;
    }

    // Define the tags to invalidate
    $tags = [
        'post-type-' . $post->post_type,
        'slug-' . $post->post_name
    ];

    // Send webhook to Next.js
    wp_nextjs_send_revalidation_request($tags);
}

function wp_nextjs_send_revalidation_request($tags) {
    $next_api_url = 'https://your-nextjs-site.com/api/revalidate';
    $secret_token = 'YOUR_SHARED_SECRET_TOKEN'; // Match REVALIDATION_SECRET in Next.js env

    $body = wp_json_encode(['tags' => $tags]);

    wp_remote_post($next_api_url, [
        'headers'     => [
            'Content-Type'  => 'application/json',
            'Authorization' => 'Bearer ' . $secret_token,
        ],
        'body'        => $body,
        'data_format' => 'body',
        'blocking'    => false, // Non-blocking request so WordPress editor doesn't lag
        'timeout'     => 5,
    ]);
}

Using 'blocking' => false is highly recommended. It ensures the WordPress editor remains fast and does not wait for the Next.js server to rebuild the cache before saving the post.

---

Step 4: Handle ACF fields and relationships

If you use Advanced Custom Fields (ACF) to build your pages, saving an ACF field group may not trigger the default post transition hooks correctly, or you might need to invalidate related pages. For example, if you update an ACF relationship field on a "Project" post, you need to revalidate the "Service" page that displays it.

To handle this, use the ACF acf/save_post hook. This hook runs after ACF has saved the metadata to the database, ensuring Next.js fetches the updated custom fields.

add_action('acf/save_post', 'wp_nextjs_acf_revalidation', 20);

function wp_nextjs_acf_revalidation($post_id) {
    // Ensure it is a valid post ID, not options page
    if (!is_numeric($post_id)) {
        return;
    }

    $post = get_post($post_id);
    if (!$post || $post->post_status !== 'publish') {
        return;
    }

    $tags = [
        'post-type-' . $post->post_type,
        'slug-' . $post->post_name
    ];

    // Example: If this post has a relationship field pointing to another page
    $related_page_id = get_field('related_page', $post_id);
    if ($related_page_id) {
        $related_post = get_post($related_page_id);
        if ($related_post) {
            $tags[] = 'slug-' . $related_post->post_name;
        }
    }

    wp_nextjs_send_revalidation_request($tags);
}

Ensure that "Show in REST API" is enabled on your ACF field groups so that your Next.js fetch requests can access the updated data under the acf key in the JSON response.

---

How to test and debug the revalidation flow

  1. Check WordPress HTTP Requests: Install a plugin like Query Monitor or Log HTTP Requests to verify that WordPress is firing the POST request to Next.js when you hit "Update".
  2. Inspect Next.js Server Logs: Monitor your Next.js console logs. If you see a 401 Unauthorized error, double-check that your REVALIDATION_SECRET env variable exactly matches the token in your WordPress PHP file.
  3. Verify Cache Status: Check the x-nextjs-cache response header on your frontend. It should change from HIT to STALE or MISS immediately after you update the post in WordPress and refresh the frontend page.

---

Checklist

  • Next.js fetch requests are configured with specific, granular tags.
  • Shared secret token is generated and added to Next.js environment variables.
  • WordPress webhook uses transition_post_status to avoid revision and auto-save triggers.
  • Webhook requests use 'blocking' => false to protect WordPress editor performance.
  • ACF field groups have "Show in REST API" enabled.
  • Webhook payload and Authorization headers are verified via a local testing tool or server logs.
Reference documentation

Drafted with AI assistance. Code targets current WordPress, WooCommerce and Next.js APIs; test changes on a staging site before production.

Need a fast, modern website?

I'm Sheraz Ahmad, a senior WordPress and headless Next.js developer with 7+ years of shipping high-performance sites. Open to permanent roles, contracts and freelance projects.

Let's talk