On-demand static revalidation for App Router apps

Version:

On-demand static revalidation (OSR) allows published Sitecore content to refresh in your Next.js App Router application without requiring a full redeploy, using tag-based cache invalidation wired in by default.

OSR is optional. You can adopt it when you use Next.js Cache Components and want publish-driven or tag-driven invalidation. You can also use Cache Components with time-based expiration only, or skip OSR entirely and fetch content dynamically.

The Content SDK now ships two App Router starter templates. OSR is optional in both:

  • nextjs-app-router - the standard template where you can choose your own caching strategy.

  • nextjs-app-router-cache-components - the standard template but with Cache Components and a revalidation route handler out-of-the-box.

Both templates share the same routing ([site]/[locale]/[[...path]]), middleware, SitecoreClient, preview/editing, and i18n support.

Note

Preview and editing always remain dynamic as they call SitecoreClient directly and are never cached. OSR is available only in Content SDK 2.2 or later.

Choosing a caching strategy

You can choose a caching strategy based on your requirements. Listed below are some of the common scenarios:

Requirement

Approach

Content updates within minutes or hours

Use Cache Compnents with a cacheLife profile. No webhook is needed

Content must appear immediately after publish

Wire a Sitecore webhook to POST /api/revalidate.

Invalidation based on an operation or selection

Call the endpoint manually with a tags[] payload

No caching for published content

Skip Cache Component helpers entirely and fetch content dynamically via SitecoreClient

Template contents

The cache-components template includes the following out of the box:

Area

Behavior

Cache Components

Enabled in next.config.ts

Cached reads

getSitecorePage, getSitecoreDictionary, getSitecoreErrorPage in src/lib/cache/

Revalidation route

Exposed as POST /api/revalidate via createSitecoreRevalidateRouteHandler

Dictionary caching

SDK in-process dictionary cache disabled in sitecore.config.ts; invalidation flows through Cache Components only

Flow of OSR

On-demand static revalidation happens in three phases:

  1. Read path - When a visitor first requests a page, the getSitecorePage or getSitecoreDictionary fetches data from Sitecore and stores the result in Next.js's built-in data cache attaching sc:* tags to the results.

  2. Invalidation path - After a publish, Sitecore fires a webhook that POSTs to /api/revalidate. The handler, createSitecoreRevalidateRouteHandler, reads the publish payload, resolves which sc:* tags correspond to the updated items, and calls Next.js's native revalidateTag for each one.

  3. Next request - Affected cache entries are marked as stale. This is not served to the next visitor and the page fetches fresh data from Sitecore using getSitecorePage or getSitecoreDictionary again.

Cache tag helpers

The SDK computes tags via public helpers. Cached reads and revalidation share the same tags:

Tag

Purpose

Example

sc:route:{site}:{locale}:{path}

URL-level

sc:route:default:en:about

sc:item:{id}:{locale}:{version}

Item-level

sc:item:71b0ba07…:en:latest

sc:dict:{site}:{locale}

Dictionary

sc:dict:default:en

  • Pages - getSitecorePage attaches route and item tags via collectSitecorePageCacheTags.

  • Dictionary - getSitecoreDictionary attaches a dictionary tag via buildSitecoreDictionaryCacheTag.

  • Personalization - Variant pages are isolated by URL path. When PersonalizeProxy identifies a variant, it rewrites the request to an internal path encoding the variant ID (for example, /default/en/about/_variantId_hero-b). Each unique path gets its own cache entry, so variants never overwrite each other. The sc:route:… tag attached always uses the clean URL (variant segments are stripped), while the sc:item:… tag is shared across all variants of the same page item. This means a publish webhook invalidating sc:item:{pageItemId}:en:latest clears all variant cache entries for that page at once. For component A/B testing with multiple simultaneous experiments, each variant combination produces a distinct cached response, all of which are invalidated together via the shared item tag. To invalidate a single specific variant URL explicitly, send its full sc:route:… tag in the tags[] payload

There is currently no cache tag per variant. Invalidating either the shared sc:item:… tag (typical publish webhook) or the sc:route:… tag (normalized path, variant segments stripped) clears all variant cache entries for that page. The variants are isolated when stored. But when it comes to clearing the cache, you can't pick and choose which ones to invalidate.

Note

Publish webhooks typically invalidate item tags. Route tags are only cleared when the full sc:route:… string is explicitly sent in a revalidate request.

Revalidation endpoint

The Content SDK exposes createSitecoreRevalidateRouteHandler from @sitecorecontent-sdk/nextjs/route-handler. It is a factory that returns a Next.js App Router POST handler. You can mount it to any route, the cache-components template wires it at POST /api/revalidate via src/app/api/revalidate/route.ts. The handler accepts an object of SitecoreRevalidateRouteHandlerOptions.

The following are the parameters required by the revalidation endpoint:

Parameter

Type

Description

secret

String

Default: undefined

Shared secret for POST /api/revalidate. If omitted here, the handler falls back to process.env.SITECORE_REVALIDATE_SECRET. When a non-empty value is set (via this option or env), callers must pass it in the x-revalidate-secret request header.

defaultLocale

String

Default: 'en'

Locale used for item tags when culture is missing, and for dictionary tags when a site has no language configured.

cacheProfile

RevalidateTagCacheProfile

Default: "max" (recommended)

Next.js revalidateTag cache profile (second argument). Accepts a string matching a cacheLife profile in next.config, or an object with { expire } per Next.js docs.

sites

SiteInfo[]

Default: undefined

List of sites (for example, from .sitecore/sites.json). Adds one sc:dict:<site>:<locale> tag per site on every revalidation call. generateSites always includes the configured default site.

Flow of revalidation

There are two ways in which revalidation can occur:

  1. The primary flow is using a Sitecore webhook. Point your Experience Edge/Content Operations webhook at your app URL. The handler processes the standard payload:

    • updates[] — Each publish row's identifier (with -media/-layout stripped) is mapped to sc:item:{id}:{locale}:latest. Locale is derived from entity_culture, or falls back to the app's default language.

    • Dictionary tags - One sc:dict:{site}:{locale} tag per site from .sitecore/sites.json is revalidated on every call.

  2. You can also revalidate without webhooks using a tags payload that the endpoint accepts.

    { "tags": ["sc:route:default:en:about", "sc:item:…"] } 

    tags[] value

    Behavior

    Starts with sc:

    Revalidated as-is

    Bare item ID

    Mapped to sc:item:{id}:{defaultLocale}:latest

If you have suggestions for improving this article, let us know!