# Upgrade the Next.js App Router SDK to the developer preview

> Move a Next.js App Router app from SDK version 20.81 to the developer preview. Do the minimum steps first, then turn on edge personalization.

Source: https://docs.uniform.app/docs/sdk/nextjs-app-router/upgrade-to-developer-preview

This page tells you how to move an app from version 20.81.x of `@uniformdev/next-app-router` to the developer preview, version `20.81.1-alpha.25.sha-f076f9b857`. If your app uses version 1 of the SDK (`@uniformdev/canvas-next-rsc`), first do the steps in [Upgrade from v1](https://docs.uniform.app/docs/sdk/nextjs-app-router/upgrade-from-v1).

The upgrade has two parts:

1. **[The minimum steps](#part-1-the-minimum-steps).** After these steps, the app builds and runs on the developer preview in lite mode. The browser chooses the personalization and test variants.
2. **[Turn on edge mode](#part-2-turn-on-edge-mode).** After these steps, the edge chooses the variants of each visitor before the first paint.

For a full example, refer to the changes in the Hello World starter: [uniformdev/examples#341](https://github.com/uniformdev/examples/pull/341).

## What changes

| Area | Version 20.81 | Developer preview |
| --- | --- | --- |
| Middleware | Calls the Route API, evaluates personalizations and tests, and applies redirects and 404s | Makes no network calls in lite mode. It writes the request state into the code. |
| Cached pages | One page for each combination of variants | One page for each route |
| Variants | Chosen in the middleware | Chosen in the browser (lite mode) or at the edge (edge mode) |
| Redirects and 404s | Applied in the middleware | Applied on the page by `requireComposition` |
| Change the composition data | A custom `DataClient` | Resolve the composition on the page, and change it there |
| Edge personalization | Not available | `vercelUniformEdgeMiddleware` or `uniformEdgeMiddleware` |

> **Warning:**
>
> Version 20.81 chose the variants in the middleware, so visitors saw no variant change. After Part 1, the server renders the default variants, and the browser changes them after hydration. Visitors who do not get the default variants see the change. Do Part 2 to keep the first paint correct.

---

## Part 1: The minimum steps

### Step 1: Install the packages

Move all `@uniformdev` packages to the same developer preview version:

```bash
v=20.81.1-alpha.25.sha-f076f9b857
npm install --save-exact @uniformdev/next-app-router@$v @uniformdev/next-app-router-client@$v \
  @uniformdev/next-app-router-shared@$v @uniformdev/context@$v
```

Install every other `@uniformdev` package of your app with the same version, for example `@uniformdev/canvas`, `@uniformdev/insights` and `@uniformdev/cli`.

A peer dependency range such as `^20.72` does not include developer preview versions. Thus npm can show an `ERESOLVE` error for a package such as `@uniformdev/toolbar-react`. Then add an override to `package.json`, so that the package uses the version of your app:

`package.json`

```json
{
  "overrides": {
    "@uniformdev/context": "$@uniformdev/context"
  }
}
```

Do not install `@uniformdev/context-edge`. The `edge` and `vercel` entry points of `@uniformdev/next-app-router` contain it.

### Step 2: Remove the removed middleware options

Keep `uniformMiddleware` or `handleUniformRoute` for now.

- Remove the `dataClient` and `pathPatternsWithVariations` options.
- The middleware no longer reads the query strings of the project map nodes. If your nodes declare query strings, list them in the [`queryStrings` option](https://docs.uniform.app/docs/sdk/nextjs-app-router/middleware#keep-query-strings-in-the-route).
- Keep the file as `middleware.ts`, with `runtime: "experimental-edge"`. Do not rename it to `proxy.ts`.

### Step 3: Update the composition route

`UniformComposition` keeps its props, but not `dataClient`. `resolveRoute` is now optional, and its default is `resolveRouteFromCode`.

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

```tsx
import {
  createUniformStaticParams,
  UniformComposition,
  type UniformPageParameters,
} from "@uniformdev/next-app-router";
import { resolveComponent } from "@/components/resolveComponent";

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

export default async function UniformPage(props: UniformPageParameters) {
  const { code } = await props.params;
  return <UniformComposition code={code} resolveComponent={resolveComponent} />;
}
```

- `createUniformStaticParams` now makes one code for each path, not one code for each variant combination.
- List each `defaultConsent` value that your middleware can write in the `defaultConsent` option. If the middleware does not set `defaultConsent`, leave out the option.

For more information, refer to [Static generation (ISR)](https://docs.uniform.app/docs/sdk/nextjs-app-router/incremental-static-regeneration-isr).

If a page renders more than one composition, wrap them in one `UniformContext`. Render each composition with `UniformResolvedComposition`:

```tsx
<UniformContext result={result} clientContextComponent={CustomUniformClientContext}>
  <UniformResolvedComposition result={result} resolveComponent={resolveComponent} />
  <UniformResolvedComposition result={footer} resolveComponent={resolveComponent} />
</UniformContext>
```

### Step 4: Clean up the server configuration

- Remove `middlewareRuntimeCache`.
- Remove `experimental.disableSwrMiddlewareCache`.
- Add your custom personalization selection algorithms to `context.personalizationSelectionAlgorithms`. Refer to [Custom personalization algorithms](https://docs.uniform.app/docs/sdk/nextjs-app-router/personalization#custom-personalization-algorithms).
- Set `defaultConsent` in your file. Your file replaces the default configuration, so a missing `defaultConsent` is `false`.

### Step 5: Replace the removed APIs

| Removed API | Use this |
| --- | --- |
| `DataClient`, `DefaultDataClient`, `EnhanceRouteOptions` | Resolve the composition on the page, and change it before render. Refer to [Change the composition data before render](https://docs.uniform.app/docs/sdk/nextjs-app-router/resolving-compositions#change-the-composition-data-before-render). |
| The `dataClient` option of the middleware, `UniformComposition` and `resolveRouteFromCode` | Remove it. |
| `precomputeComposition` | Edge mode (Part 2). The edge chooses the variants of each visitor. |
| `expireMiddlewareCacheTag` | `onRevalidateTags` of `createPreviewPOSTRouteHandler` (Part 2). |
| `pageState.components`, `pageState.rules`, `pageState.quirks`, `pageState.isPrefetch`, `pageState.requestPath` | No replacement. The code no longer contains evaluation results. Read quirks in the browser with `useQuirks`. |
| `result.code` of `ResolvedRouteResult` | No replacement. |
| `getRuleId`, `resolveComponentFromPageState`, `resolveRuleFromPageState`, `PageStateComponent`, `PageStateComponentFields` (from `@uniformdev/next-app-router-shared`) | No replacement. |
| The types `GetRouteOptions`, `GetRouteFromMiddlewareOptions`, `GetRouteFromPageStateOptions`, `RewriteRouteOptions`, `RewriteRouteResult` | No replacement. |
| `ClientContextTestTransfer` (from `@uniformdev/next-app-router-client`) | No replacement. |

### Step 6: Build and do a test

1. Run `npm run build`. Make sure that the build passes.
2. Run `npm run start`, and open a page that has a personalization.
3. Make sure that the page shows the default variant first, and then the variant of the visitor.
4. Open the page in Canvas preview. Make sure that you can edit it.

The app now uses the developer preview in lite mode.

---

## Part 2: Turn on edge mode

Do these steps after Part 1. They use the Vercel edge middleware. For other hosts, refer to [Set up edge mode on other hosts](https://docs.uniform.app/docs/sdk/nextjs-app-router/edge-mode#set-up-edge-mode-on-other-hosts).

### Step 7: Install the Vercel functions package

```bash
npm install @vercel/functions
```

### Step 8: Replace the middleware

Replace `uniformMiddleware` or `handleUniformRoute` with `vercelUniformEdgeMiddleware`. Keep your options, and add the `missing` header rule to the matcher:

`middleware.ts`

```ts
import { vercelUniformEdgeMiddleware } from "@uniformdev/next-app-router/vercel";

export default vercelUniformEdgeMiddleware({
  // your middleware options, for example rewriteRequestPath and rewriteDestinationPath
});

export const config = {
  matcher: [
    {
      source: "/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)",
      // The middleware fetches the cached page with this header. Do not run it again.
      missing: [{ type: "header", key: "x-uniform-edge-origin" }],
    },
  ],
  runtime: "experimental-edge",
};
```

The middleware loads the published Context manifest at runtime, and keeps it in the Vercel runtime cache.

### Step 9: Prebuild the two edge mode values

The default filter of `vercelUniformEdgeMiddleware` skips pages that it learns have no placements. These pages get a code without the edge mode flag. Thus prebuild the two values:

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

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

### Step 10: Expire the edge cache from the preview route

`app/api/preview/route.ts`

```ts
import {
  createPreviewGETRouteHandler,
  createPreviewOPTIONSRouteHandler,
  createPreviewPOSTRouteHandler,
} from "@uniformdev/next-app-router/handler";
import { expireVercelRuntimeCacheTags } from "@uniformdev/next-app-router/vercel";

export const GET = createPreviewGETRouteHandler();
export const POST = createPreviewPOSTRouteHandler({ onRevalidateTags: expireVercelRuntimeCacheTags });
export const OPTIONS = createPreviewOPTIONSRouteHandler();
```

`expireVercelRuntimeCacheTags` expires the manifest and the edge route records in the Vercel runtime cache. A webhook expires the records only of static project map paths. Refer to [Choose the pages for the edge](https://docs.uniform.app/docs/sdk/nextjs-app-router/edge-mode#choose-the-pages-for-the-edge).

Make sure that a Uniform webhook sends these events to `/api/preview`: `manifest.published`, `composition.*`, `entry.*`, `projectmap.node.*` and `redirect.*`. Refer to [Caching](https://docs.uniform.app/docs/sdk/nextjs-app-router/caching#webhooks).

### Step 11: Do a test of edge mode

Edge mode works only on published pages from the cache. Do the test on a production build:

1. Build and start the app:

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

   The build output shows the prebuilt codes of `/uniform/[code]`, two for each path that has a composition.
2. Look at the headers of a published page:

   ```bash
   curl -sI http://localhost:3000/
   ```

   The response has `cache-control: private, no-store`, or `private, no-cache` with an `etag`.
3. Open a page that has a personalization, with a URL that activates a signal. For example, use `?utm_campaign=launch` for a signal that reads the `utm_campaign` query string. Look at the HTML:

   ```bash
   curl -s "http://localhost:3000/?utm_campaign=launch"
   ```

   The HTML contains only the personalized variant. The `__UNIFORM_DATA__` script contains the scores of the visitor.
4. Open the page in a browser. Make sure that the personalized variant shows on the first paint, and that the console shows no hydration errors.
5. Deploy to Vercel, and publish a change in Uniform. Make sure that the page shows the change.

---

## Behavior changes

- **Redirects:** the page applies Uniform redirects. A 301 or 308 redirect gives a 308. All other redirects give a 307. In draft and editor states, the Route API ignores redirects.
- **404s:** the page calls `notFound()`. The middleware no longer rewrites to `/404`. Use `app/not-found.tsx` for the 404 page.
- **Query strings:** the middleware removes query strings from the route path, unless you list them in `queryStrings`. Thus campaign parameters such as `utm_campaign` do not make a new cached page.
- **Visibility rules:** they run only in the browser. A component with visibility rules is not in the server HTML.
- **Quirks:** the middleware sends quirks to the browser in the `ufqc` cookie for each request that has quirks. The middleware no longer checks consent or changes before it sets the cookie.
- **Page codes:** the code format changed from `2~…` to `3~…`. Links to `/uniform/2~…` paths do not work.
- **Default consent:** the middleware always writes the default consent into the code.
- **Personalization props:** `PersonalizeProps.indexes` is now `defaultIndexes`, and `TestProps.index` is now `defaultIndex`. `PersonalizeProps` also has a new `personalization` prop. This affects only custom components that replace the SDK placement components.
- **`UniformPlayground`:** it takes `code` with an optional `resolveRoute`, or a `result`. With `code`, a missing pattern gives a 404.
- **Edge state script:** `UniformContext` adds the `__UNIFORM_DATA__` script on pages in edge mode. Do not add it to your layout.
- **Cached resolvers:** the `resolveRouteFromCode` of `@uniformdev/next-app-router/cache` now caches only the published state. Draft and editor requests do not use the cache.
- **Page state:** `PageState.defaultConsent` is now a required `boolean`. Code that makes a `PageState` must set it.
- **Slugs:** the new `resolveCompositionBySlug` finds the composition ID from the slug, and then gets the composition by its ID. After a publish that changes the slug, the old slug gives no composition.

## Checklist

**Part 1: the minimum steps**

- [ ] All `@uniformdev` packages use `20.81.1-alpha.25.sha-f076f9b857`.
- [ ] `middleware.ts` has no `dataClient` or `pathPatternsWithVariations` option, and has `runtime: "experimental-edge"`.
- [ ] `UniformComposition` has no `dataClient` prop.
- [ ] `generateStaticParams` lists each `defaultConsent` value of the middleware.
- [ ] The server configuration has no `middlewareRuntimeCache` or `experimental` option, and sets `defaultConsent`.
- [ ] No code uses the removed APIs.
- [ ] `npm run build` passes, and Canvas preview works.

**Part 2: edge mode**

- [ ] `@vercel/functions` is installed.
- [ ] `middleware.ts` uses `vercelUniformEdgeMiddleware`, and the matcher has the `missing` rule for `x-uniform-edge-origin`.
- [ ] `generateStaticParams` uses `edgeMode: [true, false]`.
- [ ] The POST preview handler has `onRevalidateTags: expireVercelRuntimeCacheTags`.
- [ ] A page with a personalization shows the correct variant in the first HTML response.
