---
title: How to revalidate Sitecore pages on publish with Next.js
description: Learn how to use a Sitecore Experience Edge webhook and Next.js revalidatePath to refresh cached Sitecore Content SDK pages after each publish.
url: "https://vercel.com/kb/guide/update-sitecore-content-manually"
published: 2026-09-09
last_updated: 2026-10-26
authors: Pat Beecher
install_vercel_plugin: npx plugins add vercel/vercel-plugin
---

Use a Sitecore Experience Edge `OnEnd` webhook to refresh cached pages in a Next.js App Router app using Sitecore Content SDK. Follow this approach when your Next.js app uses static rendering or ISR in production and you want publish-driven invalidation instead of a time-based interval.

This guide applies to the standard `nextjs-app-router` template with Cache Components disabled and implements custom path-based invalidation (`revalidatePath('/', 'layout')`) rather than the tag-based revalidation included with the `nextjs-app-router-cache-components` template.

## Overview

In this guide, you'll:

- Add a protected `/api/revalidate` Route Handler to a Sitecore Content SDK app.
  
- Register a Sitecore Experience Edge webhook using the Admin API.
  
- Test the endpoint locally and against a deployed HTTPS URL.
  
- Understand the tradeoff between broad on-demand revalidation and time-based revalidation.
  

## Prerequisites

- A Next.js App Router app created from the [Sitecore Content SDK](https://doc.sitecore.com/sai/en/developers/content-sdk/20/getting-started-with-next-js-app-router-using-content-sdk.html) [`nextjs-app-router`](https://doc.sitecore.com/sai/en/developers/content-sdk/20/getting-started-with-next-js-app-router-using-content-sdk.html) [template.](https://doc.sitecore.com/sai/en/developers/content-sdk/20/getting-started-with-next-js-app-router-using-content-sdk.html)
  
- Static rendering or ISR for the Sitecore routes you want to refresh.
  
- Sitecore Experience Edge administration credentials.
  
- `curl`, `openssl`, and `jq` installed locally.
  

## How it works

Sitecore sends an `OnEnd` webhook at the end of each Experience Edge publishing job. The webhook calls a Next.js Route Handler at `/api/revalidate`, and the handler revalidates the pages using:

```typescript
revalidatePath('/', 'layout');
```

In the Content SDK App Router template, revalidating the root layout revalidates all pages beneath it, including routes for different sites and locales. Navigation and shared content refresh when those pages regenerate and fetch fresh data.

The call does not render every page during the webhook request. The next request to a cached page regenerates that page.

## Steps

### 1\. Add an environment variable

The Sitecore webhook sends a secret value in the `x-revalidate-secret` header.

Generate a random value with:

```bash
openssl rand -hex 32
```

Add this variable to `.env.local` and your deployment’s environment variables.

```bash
SITECORE_REVALIDATE_SECRET=your_revalidate_secret_here
```

### 2\. Add the webhook Route Handler

Create a new `src/app/api/revalidate/route.ts` file:

```typescript
import { revalidatePath } from 'next/cache';
import { NextResponse } from 'next/server';

const SECRET_HEADER = 'x-revalidate-secret';

export async function POST(request: Request) {
  const configuredSecret = process.env.SITECORE_REVALIDATE_SECRET?.trim();

  if (!configuredSecret) {
    return NextResponse.json(
      { error: 'SITECORE_REVALIDATE_SECRET is not configured.' },
      { status: 500 }
    );
  }

  if (request.headers.get(SECRET_HEADER) !== configuredSecret) {
    return NextResponse.json({ error: 'Unauthorized.' }, { status: 401 });
  }

  let body: unknown;

  try {
    body = await request.json();
  } catch {
    return NextResponse.json(
      { error: 'Request body must be valid JSON.' },
      { status: 400 }
    );
  }

  if (typeof body !== 'object' || body === null || Array.isArray(body)) {
    return NextResponse.json(
      { error: 'Request body must be a JSON object.' },
      { status: 400 }
    );
  }

  revalidatePath('/', 'layout');

  return NextResponse.json({
    revalidated: true,
    path: '/',
    type: 'layout',
  });
}
```

If your Sitecore routes live under a different shared layout, pass that layout path instead of `/`.

### 3\. Create the Experience Edge webhook

To register the webhook with the Sitecore Experience Edge, first generate the client ID and secret to access the Sitecore APIs:

1. Go to https://deploy.sitecorecloud.io/
   
2. Click **Credentials →** **Create credential**.
   
3. In the **​Create New Client​​** dialog, enter a name and description for the client. Then click **Create**. The ​Client ID and Client Secret​​ appear.
   
4. Copy the Client ID and Client Secret because you won't be able to view them again. You'll use them to request an access token.
   

With the client ID and secret, generate a JSON Web Token (JWT) using the API:

```bash
CLIENT_ID=your_client_id_here
CLIENT_SECRET=your_client_secret_here

TOKEN=$(
  curl -sS -X POST 'https://auth.sitecorecloud.io/oauth/token' \
    -H 'Content-Type: application/json' \
    --data-raw "{
      \"audience\": \"https://api.sitecorecloud.io\",
      \"grant_type\": \"client_credentials\",
      \"client_id\": \"$CLIENT_ID\",
      \"client_secret\": \"$CLIENT_SECRET\"
    }" |
    jq -r '.access_token'
)
```

List existing webhooks before creating another one:

```bash
curl -sS 'https://edge.sitecorecloud.io/api/admin/v1/webhooks' \
  -H "Authorization: Bearer $TOKEN" |
  jq
```

Create the `OnEnd` webhook, which executes at the end of the Experience Edge publishing job.

**Note:** To set the `REVALIDATE_URL` variable, deploy the project, then set `REVALIDATE_URL` to `/api/revalidate` on its stable production domain.

If your project is behind Deployment Protection, go to **Security → Deployment Protection** and generate a **Protection Bypass for Automation** token**.** Add that value to your Sitecore webhook headers:

```bash
REVALIDATE_URL='https://your-domain.com/api/revalidate'
SITECORE_REVALIDATE_SECRET='your_revalidate_secret_here'
X_VERCEL_PROTECTION_BYPASS='your_protection_bypass_token_here'

curl -sS -X POST 'https://edge.sitecorecloud.io/api/admin/v1/webhooks' \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -d "$(
    jq -n \
      --arg uri "$REVALIDATE_URL" \
      --arg secret "$SITECORE_REVALIDATE_SECRET" \
      --arg bypass "$X_VERCEL_PROTECTION_BYPASS" \
      '{
        label: "Next.js broad revalidatePath",
        uri: $uri,
        method: "POST",
        headers: {
          "x-revalidate-secret": $secret,
          "x-vercel-protection-bypass": $bypass,
          "Content-Type": "application/json"
        },
        body: "{}",
        createdBy: "your_name_here",
        executionMode: "OnEnd"
      }'
  )" |
  jq
```

Use `OnEnd` because this handler invalidates all pages under the root layout and does not need individual item updates. `OnUpdate` splits a large update set into multiple webhook requests, resulting in repeated broad invalidations for a single publish. Set the request’s content type to `application/json` and configure its body as `{}` because the handler expects a JSON object. To learn more, see the [Sitecore webhook execution modes](https://doc.sitecore.com/sai/en/developers/sitecoreai/experience-edge/webhook-objects/the-webhook-execution-modes.html) and the [Sitecore webhook objects](https://doc.sitecore.com/sai/en/developers/sitecoreai/experience-edge/webhook-objects/webhook-objects.html) reference.

### 4\. Test locally

Build and run the production server. Development mode does not prove the ISR behavior.

```bash
npm run build
npm run start
```

Test the secret by sending a request without the secret:

```bash
curl -i -X POST 'http://localhost:3000/api/revalidate' \
  -H 'Content-Type: application/json' \
  -d '{}'
```

The endpoint should return `401 Unauthorized`:

```txt
HTTP/1.1 401 Unauthorized
```

Test a valid webhook request:

```bash
curl -sS -X POST 'http://localhost:3000/api/revalidate' \
  -H 'Content-Type: application/json' \
  -H "x-revalidate-secret: $SITECORE_REVALIDATE_SECRET" \
  -d '{}' |
  jq
```

Expected response:

```json
{
  "revalidated": true,
  "path": "/",
  "type": "layout"
}
```

Test the deployed endpoint with the same request:

```bash
curl -sS -X POST 'https://your-domain.com/api/revalidate' \
  -H 'Content-Type: application/json' \
  -H "x-revalidate-secret: $SITECORE_REVALIDATE_SECRET" \
  -d '{}' |
  jq
```

To test a real Sitecore webhook locally, expose your local server with an HTTPS tunnel and set `REVALIDATE_URL` to the tunnel URL before creating the webhook.

## How this compares with time-based revalidation

When publishing content infrequently, broad on-demand revalidation can reduce unnecessary regeneration compared to a short time-based interval, such as 5s. It invalidates cached content in response to publication rather than elapsed time. If content is published frequently, broad invalidation may cause more regeneration than a longer time-based interval.

For a 10,000-page site, `revalidatePath('/', 'layout')` does not regenerate 10,000 pages immediately during the webhook request. It invalidates all cached data. Each affected path regenerates on its next visit.

Time-based revalidation also works per request. If a page has a 5s revalidation interval, the first request after the interval can serve stale content and trigger background regeneration. On high-traffic pages, that can happen repeatedly even when Sitecore content has not changed. On pages with no traffic, nothing regenerates until a request arrives.

The tradeoff is the cache hit rate after publication. Broad on-demand revalidation can make many cached pages stale at once, so popular pages regenerate after each publish. If the site publishes often and has high traffic, move toward targeted `revalidatePath` calls, tag-based revalidation, or a page-to-datasource dependency index.

## Troubleshooting

- If pages never change, confirm that the routes are static or that ISR is enabled in the production build. Fully dynamic pages render on each request. If content remains stale, inspect any cached data reads and upstream caches.
  
- If the endpoint returns `401`, the webhook header does not match the `SITECORE_REVALIDATE_SECRET` value. Ensure that you’ve set the correct value in the Vercel project’s environment settings.
  
- If the endpoint returns `400`, ensure the body is valid JSON and is an object.
  
- If the endpoint returns `500`, set `SITECORE_REVALIDATE_SECRET` in the environment. Ensure that you’ve set the correct value in the Vercel project’s environment settings. Also, confirm you’ve set the value in the correct environment (e.g. Preview or Production).
  
- If Sitecore stops calling the endpoint, inspect the webhook with `GET /webhooks/{id}` and check `lastRuns`.
  
- If the webhook is disabled, fix the endpoint and re-enable it. Sitecore disables a webhook after 10 consecutive failures, and a request that takes longer than 30s counts as a failure.
  

## Next steps

- Read the [Next.js](https://nextjs.org/docs/app/api-reference/functions/revalidatePath) [`revalidatePath`](https://nextjs.org/docs/app/api-reference/functions/revalidatePath) [reference](https://nextjs.org/docs/app/api-reference/functions/revalidatePath) to learn more.
  
- Explore [Next.js caching without Cache Components](https://nextjs.org/docs/app/guides/caching-without-cache-components).
  
- Learn about the cost impact in [Vercel’s ISR usage and pricing](https://vercel.com/docs/incremental-static-regeneration/limits-and-pricing).
  
- Review the [Sitecore Experience Edge Admin API](https://doc.sitecore.com/sai/en/developers/sitecoreai/experience-edge/experience-edge-apis/admin-rest-api.html).
  
- See the [Sitecore webhook objects](https://doc.sitecore.com/sai/en/developers/sitecoreai/experience-edge/webhook-objects/webhook-objects.html) to learn about the webhook fields.

## Related Documentation

- [Next.js](/docs/frameworks/full-stack/nextjs)
- [Vercel CDN](/docs/cdn)

## More Next.js guides

- [Migrate self-hosted Next.js and containers from AWS to Vercel](/kb/guide/migrate-containers-from-aws-to-vercel): Migrate Next.js apps and HTTP containers from AWS to Vercel. Configure AWS access, assess background workers, and plan testing, DNS cutover, and rollback.
- [Migrate a Next.js app from Webflow Cloud to Vercel](/kb/guide/migrate-a-next-js-app-from-webflow-cloud-to-vercel): Move your Next.js app from Webflow Cloud to Vercel: remove the OpenNext Cloudflare adapter, drop the base path, map storage bindings to Vercel Blob, Redis, and Postgres, and deploy with Git or the vercel CLI.
- [Sitemap lastmod should track content, not deployments](/kb/guide/correct-lastmod-nextjs-sitemap): Set accurate sitemap lastmod dates in Next.js using stored content timestamps, targeted revalidation, and checks that distinguish content changes from builds.