← Back to index

4.4 Next.js Headless WordPress (ISR + On-Demand Revalidation)

KOTA · static explanation (no live WP — pattern from production stack)

Incremental Static Regeneration keeps pages fast while WordPress remains the editorial source. Time-based revalidate refreshes stale HTML on a schedule; tag-based revalidateTag lets WP webhooks bust the cache when content changes.

App Router page — ISR window

Fetch from the REST API and set a revalidation interval (seconds).

// app/page.js
import { getHomepageData } from '@/lib/wordpress';

export const revalidate = 60;

export default async function HomePage() {
  const data = await getHomepageData();
  return <main>{/* render data */}</main>;
}

Tagged fetch — tie cache to a revalidation tag

Use next: { tags: ['homepage'] } so the webhook can target exactly this data.

// lib/wordpress.js
export async function getHomepageData() {
  const res = await fetch(
    `${process.env.WP_API_URL}/wp-json/wp/v2/pages?slug=home`,
    { next: { tags: ['homepage'] } }
  );
  return res.json();
}

On-demand revalidation route

WordPress (or a plugin) POSTs here after publish; the secret must match your env.

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

export async function POST(req) {
  const { secret, tag } = await req.json();
  if (secret !== process.env.REVALIDATION_SECRET)
    return NextResponse.json({ error: 'Invalid secret' }, { status: 401 });

  revalidateTag(tag);
  return NextResponse.json({ revalidated: true, tag });
}

Why this pattern

  • Editors keep using WordPress; the site stays on the edge as mostly static HTML.
  • Short ISR + webhooks avoids both stale content and hammering the CMS on every request.
  • Tags map cleanly to “homepage”, “work”, “navigation”, etc.

This repo is static HTML only; copy the snippets into a Next.js App Router project with env vars set.