Incremental Static Regeneration (ISR)
Static generation (ISR)#
By default, Uniform pages are rendered on demand per request (dynamic rendering). This is because the Uniform middleware evaluates personalization and A/B tests at the edge for each visitor, producing a unique code (serialized page state) that the app/uniform/[code]/page.tsx route renders. Since each visitor may receive a different variant, the page cannot be statically generated without knowing all possible variant combinations upfront.
As an optional, yet recommended optimization, you can use generateStaticParams with createUniformStaticParams to pre-render specific pages at build time using Next.js Incremental Static Regeneration (ISR). The SDK generates all possible page state permutations (accounting for personalization variants, A/B tests, and visibility rules) for the given paths, so every variant combination is pre-built.
This is useful for high-traffic pages where you want the fastest possible response time.
How ISR works (recommended default)#
The recommended starting point is ISR with no build-time pre-rendering. Pages are generated on their first visit and cached for all subsequent visitors. This gives you static-like performance with zero added build cost:
First request (cache miss -- page not yet generated):
Second request and beyond (cache hit):
This is enough for most sites. The only cost is a single cold-start for the very first visitor to hit each unique page state. After that, every visitor is served a static page from the nearest CDN edge node.
note
You can optimize first-visit latency further by pre-rendering high-traffic pages at build time. This trades faster builds for faster first renders. See Understanding the trade-offs below for a detailed comparison of the available strategies.
Enabling ISR (no build-time pre-rendering)#
The simplest way to enable ISR is to add generateStaticParams that returns an empty array. No pages are pre-rendered at build time, but each page is statically generated on its first request and cached. Subsequent requests for the same page state are served from cache:
/app/uniform/[code]/page.tsx
This is often enough to get meaningful performance gains without the complexity of managing a path list.
warning
cacheComponents incompatibility: If your next.config.ts has cacheComponents: true (required for the use cache directive), Next.js does not allow an empty generateStaticParams -- the build will fail. You must return at least one param so Next.js can validate the route at build time. In that case, use one of the approaches below to provide actual paths.
See the Next.js documentation on this constraint for details.
Pre-rendering specific paths#
For higher performance, you can pre-render specific pages at build time using createUniformStaticParams. Pages not in the list are still generated on first request and cached (thanks to dynamicParams defaulting to true), but pre-rendered pages are available instantly from the first request.
Understanding the trade-offs#
Pre-rendering pages with personalization and A/B testing has a cost: the SDK must generate the cartesian product of all variant combinations for each path. A page with 3 personalization slots (2 variants each) and 1 A/B test (2 variants) produces 2 x 2 x 2 x 2 = 16 page permutations -- all of which must be built. As you add more paths and more variants, build times and build output size grow multiplicatively.
This is the fundamental trade-off you should consider when deciding how many paths to pre-render.
Option A: No pre-rendering (recommended starting point)#
Build time: Fastest (seconds). First visit: On-demand render, then cached. Best for: Most sites, where a brief cold-start for the first visitor is acceptable.
Option B: Pre-render high-traffic pages#
Build time: Moderate (minutes, depending on variant count). First visit: Instant for pre-rendered pages, on-demand for the rest. Best for: High-traffic pages (homepage, landing pages) where first-visit latency matters.
Option C: Pre-render all pages#
Build time: Slowest (grows with pages x variants). First visit: Instant for every page. Best for: Small sites with few pages, or sites where every page must be fast from the very first request.
note
For most Uniform projects, Option A or B is the sweet spot. Pre-rendering every page on a site with many personalization variants can produce thousands of static pages, significantly increasing build time and deployment size. Start with no pre-rendering (Option A), measure first-visit latency, and selectively add high-traffic paths (Option B) only if needed.
How createUniformStaticParams works#
createUniformStaticParams takes an array of original visitor-facing paths (e.g., /, /about, /contact) and for each path:
- Resolves the composition via the Uniform Route API.
- Extracts all personalizations, A/B tests, and visibility rules from the composition tree.
- Generates all possible variant permutations (the cartesian product of all variants).
- Serializes each permutation into a
codevalue forgenerateStaticParams.
warning
The paths array must contain original visitor-facing paths (e.g., /about), not the internal rewritten code path (/uniform/some-code-here). The SDK resolves the code values from these paths automatically.
Basic example#
Add generateStaticParams to your app/uniform/[code]/page.tsx:
/app/uniform/[code]/page.tsx
Dynamic paths from the project map#
Instead of hard-coding paths, you can fetch them programmatically from the Uniform project map. This ensures new pages are automatically included without code changes.
For a fully working example of this pattern, see the Component Starter Kit (release-candidate branch).
You can also combine programmatic and manual paths, or filter by specific criteria:
Localized paths#
For localized sites, each locale/path combination must be included as a separate entry in the paths array. The locale parameter tells the SDK which locale to embed in the page state for route resolution:
When fetching paths from the project map dynamically, expand each node into all supported locales:
Path rewriting#
If your middleware uses rewriteRequestPath to transform paths before route resolution (for example, to prepend a locale), you can pass the same rewrite logic to createUniformStaticParams so it resolves routes the same way the middleware does:
On-demand revalidation via webhooks#
When content is published in Uniform, you want the cached static pages to update without a full site rebuild. The Uniform App Router SDK handles this automatically through on-demand revalidation -- Uniform sends a webhook to your Next.js app, and the SDK's POST handler invalidates the Next.js cache for the affected pages.
How it works#
Unlike the old Pages Router approach (which required a custom pages/api/revalidate.ts handler with res.revalidate()), the App Router SDK handles everything through the built-in POST handler in app/api/preview/route.ts. The SDK:
- Receives the webhook payload from Uniform.
- Validates the request using either
UNIFORM_PREVIEW_SECRET(query string) orUNIFORM_WEBHOOK_SECRET(Svix signature verification). - Parses the webhook event type (composition published, project map node updated, redirect changed, manifest published, etc.).
- Looks up the affected project map node paths for the changed composition.
- Calls
revalidatePath()andrevalidateTag()fromnext/cacheto purge the Next.js cache for those paths.
Supported webhook events#
The POST handler responds to these Uniform webhook events automatically:
| Event | What gets revalidated |
|---|---|
composition:published | All paths linked to the composition via project map |
composition:deleted | All paths linked to the composition via project map |
projectMapNode:insert | The inserted node's path |
projectMapNode:update | Both the current and previous path (handles renames/moves) |
projectMapNode:delete | The deleted node's path |
redirect:insert | The redirect source URL path |
redirect:update | The redirect source URL path |
redirect:delete | The redirect source URL path |
manifest:published | The manifest cache tag (personalization manifest) |
For pattern compositions (reusable across multiple pages), the SDK invalidates the route cache tag, which purges all route cache entries since a pattern change could affect any page.
Step 1: Set up the preview/revalidation handler#
If you followed the setup guide, you already have this file. The POST handler serves double duty -- it handles both visual editing preview requests and webhook-based revalidation:
No custom revalidation code is needed. The SDK handles parsing the webhook payload, resolving affected paths, and calling revalidatePath() and revalidateTag() internally.
Step 2: Set the preview secret environment variable#
Add a UNIFORM_PREVIEW_SECRET to your environment. This secret is passed as a query parameter in the webhook URL and validates that the revalidation request is coming from Uniform:
Set this same value in your hosting provider's environment variables (Vercel, Netlify, etc.).
note
You can use any strong random string as the secret. Generate one with openssl rand -base64 32 or similar. The secret must match between your environment variable and the webhook URL configured in Uniform.
Step 3: Configure the webhook in Uniform#
- Go to your Uniform project in the dashboard.
- Navigate to Settings -> Webhooks.
- Click Add webhook.
- Configure the webhook:
- URL:
https://your-site.com/api/preview?secret=your-secret-value-here - Events: Select the events you want to trigger revalidation for. At minimum, select
composition.published. For full coverage, also selectcomposition.deleted,entry.published,entry.deleted,projectMapNode.update,redirect.insert,redirect.update,redirect.delete, andmanifest.published. If you plan on using the releases, togglerelease.launched.
- URL:
- Save the webhook.
warning
Make sure not to select the *.changed webhook events when configuring the ISR handler as those fire more often during content authoring and will create unnecessary revalidation request. Specifically, ignore these webhook events: composition.changed and entry.changed
warning
The secret is passed as a query parameter (?secret=...), not as a header. Make sure the value in the URL matches your UNIFORM_PREVIEW_SECRET environment variable exactly.
Step 4: (Optional) Use Svix webhook signature verification#
For additional security, Uniform webhooks support Svix signature verification. When enabled, each webhook request includes svix-id, svix-timestamp, and svix-signature headers. The SDK automatically validates these signatures when the UNIFORM_WEBHOOK_SECRET environment variable is set:
You can find the Svix signing secret in your Uniform project's webhook settings. When UNIFORM_WEBHOOK_SECRET is set, the SDK verifies the request signature using the Svix library before processing the webhook payload. If the signature does not match, the request is rejected with a 401 response.
You can use either UNIFORM_PREVIEW_SECRET (query parameter) or UNIFORM_WEBHOOK_SECRET (Svix signature), or both for defense in depth.
Verifying the setup#
After configuring the webhook, publish a change to a composition in Uniform. You should see:
- A
POSTrequest to your/api/previewendpoint in your server logs. - The response body includes
{ "handled": true, "tags": [...], "paths": [...] }indicating which cache entries were invalidated. - The next visit to the affected page renders fresh content.
To test locally, you can use a tunnel service (like ngrok or cloudflared) to expose your local dev server to the internet, then configure the webhook URL to point to your tunnel URL.
CDN provider compatibility#
On-demand revalidation requires your hosting provider to support Next.js cache invalidation:
| Provider | Support |
|---|---|
| Vercel | Full support (including revalidateTag with edge caching) |
| Self-hosted | Supported when using the Next.js standalone output with a persistent cache directory |
| Netlify | Supported via Netlify's Next.js runtime |
| Cloudflare | Supported via Open Next adapter |
Consult your provider's documentation to ensure on-demand ISR is compatible with your deployment setup.
Manual revalidation#
If you need to trigger revalidation manually (for example, from a custom API route or a build script), use the original visitor-facing path:
Playground route#
You can also add generateStaticParams to the playground route using createUniformPlaygroundStaticParams in the same way.
note
Adding custom keys or data to page state creates new unique codes. If you do this, avoid pre-rendering those paths with generateStaticParams since the codes will not align at build time.