# Incremental Static Regeneration (ISR)

> How to configure ISR with the Uniform SDK for Next.js App Router, including on-demand revalidation via Uniform webhooks.

Source: https://docs.uniform.app/docs/sdk/nextjs-app-router/incremental-static-regeneration-isr

> **Developer Preview:**
>
> This page documents the developer preview of the SDK, version `20.81.1-alpha.25.sha-f076f9b857`. In this version, each published route has one cached page for all visitors. The stable SDK made one page for each combination of personalization and test variants.

## Static generation (ISR)

The middleware rewrites each request to `/uniform/[code]`. The code identifies the route, not the visitor. Thus Next.js can cache one page for each route, and serve it to all visitors. Personalizations and A/B tests get their variants in the browser or at the edge. Refer to [Personalization and A/B tests](https://docs.uniform.app/docs/sdk/nextjs-app-router/personalization).

The code contains these values. A different value makes a different code, and thus a different cached page:

- The route path, with the query strings that you list in [`queryStrings`](https://docs.uniform.app/docs/sdk/nextjs-app-router/middleware#keep-query-strings-in-the-route)
- The `keys` from `rewriteRequestPath`
- The composition state: published, draft or editor
- The preview mode
- The default consent
- The locale and the release
- The edge mode flag

Draft and editor requests render on each request. Next.js does not cache them.

### Render on the first visit (recommended)

Return an empty array from `generateStaticParams`. The build prerenders no pages. Next.js renders each page on its first visit, and serves it from the cache after that:

`app/uniform/[code]/page.tsx`

```tsx
// Render each page on its first visit, then serve it from the cache.
export const generateStaticParams = async () => [];
```

```mermaid
flowchart LR
  A["First request /about"] --> B["Next.js renders the page"]
  B --> C["Next.js caches the page"]
  D["Next requests /about"] --> E["Served from the cache"]
```

This is enough for most sites. Only the first visitor of each page waits for the render.

> **Warning:**
>
> With Cache Components (`cacheComponents: true`), Next.js does not accept an empty `generateStaticParams`, and the build fails. Return at least one code. Refer to the [Next.js message about empty generateStaticParams](https://nextjs.org/docs/messages/empty-generate-static-params).

### Prerender some paths

To make the first visit fast, prerender the most important pages at build time with `createUniformStaticParams`. Next.js still renders the other pages on their first visit, because `dynamicParams` is `true` by default.

`app/uniform/[code]/page.tsx`

```tsx
import { createUniformStaticParams } from "@uniformdev/next-app-router";

export const generateStaticParams = () =>
  createUniformStaticParams({ paths: ["/", "/about", "/contact"] });
```

`createUniformStaticParams` does these steps for each path:

1. It applies the `rewrite` function, if you give one.
2. It gets the published route from the Uniform Route API. A path that has no composition gives no code.
3. It makes one code for each `defaultConsent` value and each `edgeMode` value.

The build time grows with the number of paths, not with the number of variants.

> **Warning:**
>
> Put the paths of the visitors in `paths`, for example `/about`. Do not put internal code paths, for example `/uniform/3~64~L2Fib3V0~~3`.

### Consent and edge mode values

A prebuilt page is used only when its code is the same as the code from the middleware. Two values in the code depend on your middleware:

| Option | Default | Set it when |
| --- | --- | --- |
| `edgeMode` | `false` | You use an edge middleware. With `vercelUniformEdgeMiddleware`, use `[true, false]`, because the default filter skips pages that have no placements. With your own filter, give the same filter. |
| `defaultConsent` | The value of the server configuration | The middleware sets `defaultConsent` for each request. List each value that it can write, for example `[true, false]`. |

`app/uniform/[code]/page.tsx`

```tsx
export const generateStaticParams = () =>
  createUniformStaticParams({
    paths: ["/", "/about"],
    edgeMode: [true, false],
    defaultConsent: [true, false],
  });
```

This example makes a maximum of 4 codes for each path.

### Paths from the project map

Get the paths from the Uniform project map, so the build includes new pages without a code change:

`app/uniform/[code]/page.tsx`

```tsx
import { createUniformStaticParams, getProjectMapClient } from "@uniformdev/next-app-router";

async function getStaticPaths(): Promise<string[]> {
  const { nodes } = await getProjectMapClient({ cache: { type: "default" } }).getNodes({});

  // Skip nodes with dynamic segments, for example /products/:slug.
  return (nodes ?? []).map((node) => node.path).filter((path) => !path.includes(":"));
}

export const generateStaticParams = async () =>
  createUniformStaticParams({ paths: await getStaticPaths() });
```

To prerender only the top-level pages, filter the paths:

```tsx
const paths = (await getStaticPaths()).filter((path) => path.split("/").filter(Boolean).length <= 1);
```

For a full example, refer to the [Component Starter Kit](https://github.com/uniformdev/component-starter-kit-next-approuter).

### Localized paths

Put each locale and path in `paths`. For examples, refer to [Localize your app](https://docs.uniform.app/docs/sdk/nextjs-app-router/localization#static-generation).

### Path rewrites

If the middleware changes the path with `rewriteRequestPath`, give the same change to `createUniformStaticParams`. The codes are then the same as the codes from the middleware:

```tsx
export const generateStaticParams = () =>
  createUniformStaticParams({
    paths: ["/", "/about", "/contact"],
    rewrite: async ({ path }) => ({ path: `/en${path === "/" ? "" : path}` }),
  });
```

> **Note:**
>
> `keys` from `rewriteRequestPath` go into the code. If your middleware adds keys from the request, for example from a query string, the build cannot know them. Do not prerender these paths.

### Playground route

`createUniformPlaygroundStaticParams` makes the codes for the playground route. It uses only the first path of `paths`, and makes one code for each `defaultConsent` value. Playground requests are draft requests, so most apps do not need to prerender them.

---

## On-demand revalidation with webhooks

When an author publishes content, Uniform sends a webhook to your app. The `POST` handler in `app/api/preview/route.ts` revalidates the cache tags and paths of the changed content. The next request gets the old page, and starts a new render in the background. The requests after that get the new page.

```mermaid
flowchart LR
  A["Author publishes"] --> B["Uniform webhook POST"]
  B --> C{"Secret valid?"}
  C -->|"Yes"| D["Find the tags and paths"]
  D --> E["revalidateTag and revalidatePath"]
  E --> F["Next request starts a new render"]
  C -->|"No"| G["401"]
```

### Step 1: Add the POST handler

`app/api/preview/route.ts`

```ts
import { createPreviewPOSTRouteHandler } from "@uniformdev/next-app-router/handler";

export const POST = createPreviewPOSTRouteHandler();
```

### Step 2: Set the secrets

Set `UNIFORM_PREVIEW_SECRET`, `UNIFORM_WEBHOOK_SECRET`, or the two variables, in your host:

```bash
UNIFORM_PREVIEW_SECRET=your-secret-value
UNIFORM_WEBHOOK_SECRET=whsec_your-svix-signing-secret
```

You can make a strong secret with `openssl rand -base64 32`.

### Step 3: Configure the webhook in Uniform

1. In Uniform, go to **Settings > Webhooks**.
2. Add a webhook with the URL `https://your-site.com/api/preview?secret=your-secret-value`.
3. Select these events:

   - `composition.published` and `composition.deleted`
   - `entry.published` and `entry.deleted`
   - `projectmap.node.insert`, `projectmap.node.update` and `projectmap.node.delete`
   - `redirect.insert`, `redirect.update` and `redirect.delete`
   - `manifest.published`
4. Save the webhook.

For the tags that each event revalidates, and for the security checks, refer to [Caching](https://docs.uniform.app/docs/sdk/nextjs-app-router/caching#webhooks).

> **Warning:**
>
> The secret is a query string (`?secret=…`), not a header. The value in the URL must be the same as `UNIFORM_PREVIEW_SECRET`.

### Verify the setup

1. Publish a change to a composition.
2. Look for a `POST` request to `/api/preview` in the server log.
3. Make sure that the response body is `{ "handled": true, "tags": [...], "paths": [...] }`.
4. Open the page two times. The second request shows the new content.

To do a test locally, use a tunnel service, for example `ngrok` or `cloudflared`. Set the webhook URL to the tunnel URL.

### Host support

On-demand revalidation must have a host that supports Next.js cache revalidation:

| Host | Support |
| --- | --- |
| Vercel | Supported, also `revalidateTag` |
| Self-hosted | Supported with the [Next.js standalone output](https://nextjs.org/docs/app/api-reference/config/next-config-js/output#automatically-copying-traced-files) and a persistent cache folder |
| Netlify | Supported with the Netlify Next.js runtime |
| Cloudflare | Supported with the OpenNext adapter |

Read the documentation of your host to make sure that it supports on-demand ISR.

### Manual revalidation

To revalidate a page manually, revalidate its cache tag. The cached page uses the fetch tags of its route, so the tag expires the page too:

```tsx
import { revalidateTag } from "next/cache";

revalidateTag("path:/about", "max");
```

Do not use `revalidatePath` with the path of the visitor. The middleware rewrites the request to `/uniform/[code]`, and with a rewrite, `revalidatePath` must get the destination route. Refer to [revalidatePath with rewrites](https://nextjs.org/docs/app/api-reference/functions/revalidatePath#using-revalidatepath-with-rewrites). To revalidate all Uniform pages, use `revalidatePath("/uniform/[code]", "page")`.
