# Resolve compositions on a page with the Next.js App Router SDK

> Recipe: resolve Uniform compositions on the page by code, path, ID or slug. Render more than one composition, change composition data before render, and use ordinary Next.js routes.

Source: https://docs.uniform.app/docs/sdk/nextjs-app-router/resolving-compositions

> **Developer Preview:**
>
> The resolvers on this page are new or changed in the developer preview of the SDK, version `20.81.1-alpha.25.sha-f076f9b857`. They replace the data client (`DataClient`, `DefaultDataClient`) and `precomputeComposition` of the stable SDK.

**Task:** get a composition on the page yourself, and give it to the renderer.

`UniformComposition` resolves and renders one composition for the `[code]` route. Use this recipe when you must do one of these tasks:

- Render more than one composition on a page, for example a page and a shared footer.
- Change the composition data before render.
- Get a composition by its ID or slug, without the project map.
- Render a composition in an ordinary Next.js route, without `[code]`.

## The parts

| Part | Job |
| --- | --- |
| A resolver | Gets the route or composition from Uniform. It returns a `ResolvedRouteResult`. |
| `requireComposition` | Applies a redirect or a 404 from the result. |
| `UniformContext` | Starts the Uniform Context in the browser, and adds the visual editing script and the edge state script. Render it one time on each page. |
| `UniformResolvedComposition` | Renders the components of one result. |

`UniformComposition` is these four parts together:

```tsx
// What UniformComposition does
const result = requireComposition(await resolveRouteFromCode({ code }));

return (
  <UniformContext result={result} clientContextComponent={clientContextComponent}>
    <UniformResolvedComposition result={result} resolveComponent={resolveComponent} />
  </UniformContext>
);
```

## Resolvers

All resolvers are exported from `@uniformdev/next-app-router`. `@uniformdev/next-app-router/cache` exports `resolveRouteFromCode`, `resolveRouteFromPath`, `resolveCompositionById` and `resolveCompositionBySlug` with `'use cache'` for published content. Refer to [Caching](https://docs.uniform.app/docs/sdk/nextjs-app-router/caching#cache-components).

| Resolver | Input | Uses | Redirects |
| --- | --- | --- | --- |
| `resolveRouteFromCode` | `{ code }` | Route API, with the route path in the code | Yes |
| `resolveRouteFromPath` | `{ path, code }` or `{ path, pageState }` | Route API, with the path that you give | Yes |
| `resolveCompositionById` | `{ compositionId, code }` or `{ compositionId, pageState }` | Composition API | No |
| `resolveCompositionBySlug` | `{ slug, code }` or `{ slug, pageState }` | Composition API | No |
| `resolvePlaygroundRoute` | `{ code }` | Composition API, in draft mode only | No |

Each resolver must have a page state: the composition state, the locale, the release and the other values of the request. Give the `code` from the `[code]` route, or a `PageState` object. `deserializePageState({ code })` changes a code into a `PageState`.

### The result

```ts
type ResolvedRouteResult =
  | { pageState: PageState; route: RouteGetResponseEdgehancedComposition } // route.type === "composition"
  | { pageState: PageState; route: RouteGetResponseRedirect }              // route.type === "redirect"
  | { pageState: PageState; route: undefined };                            // not found
```

- A missing route or composition gives `route: undefined`.
- An API error also gives `route: undefined`. The SDK writes the error to the console as a warning.
- In the editor state, the resolver tries the editor state first, and then the draft state. `pageState.compositionState` tells you which state it found.
- In the published state, the Route API applies Uniform redirects. In other states, it ignores them.

### requireComposition

`requireComposition(result)` returns a result that always has a composition:

- **Redirect:** it calls `permanentRedirect()` for a 301 or 308 redirect, and `redirect()` for other status codes. Next.js sends a 308 or a 307 redirect.
- **Not found:** it calls `notFound()`.

`resolveRedirectHref({ route, routePath })` gives the target URL of a redirect result, if your code handles the redirects.

`UniformResolvedComposition` renders nothing for a redirect or a not-found result. Thus you can also skip `requireComposition`, and render the page without Uniform content when there is no composition.

## Render more than one composition

Render `UniformContext` one time, and put one `UniformResolvedComposition` in it for each composition:

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

```tsx
import {
  requireComposition,
  resolveCompositionBySlug,
  resolveRouteFromCode,
  UniformContext,
  UniformResolvedComposition,
  type UniformPageParameters,
} from "@uniformdev/next-app-router";
import { resolveComponent } from "@/components/resolveComponent";

export default async function UniformPage(props: UniformPageParameters) {
  const { code } = await props.params;

  const [page, footer] = await Promise.all([
    resolveRouteFromCode({ code }).then(requireComposition),
    resolveCompositionBySlug({ slug: "global-footer", code }),
  ]);

  return (
    <UniformContext result={page}>
      <UniformResolvedComposition result={page} resolveComponent={resolveComponent} />
      <UniformResolvedComposition result={footer} resolveComponent={resolveComponent} />
    </UniformContext>
  );
}
```

`UniformContext` uses the page state and the composition data of `result` for the browser Context. Give it the main composition of the page.

> **Warning:**
>
> Do not render `UniformContext` more than one time on a page. Do not put `UniformComposition` or `UniformPlayground` in a layout that renders its own `UniformContext`.

## Change the composition data before render

Resolve the composition, change it, and then render it. This replaces the `enhanceRoute` method of the data client in the stable SDK:

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

```tsx
import { enhance, EnhancerBuilder } from "@uniformdev/canvas";
import {
  requireComposition,
  resolveRouteFromCode,
  UniformContext,
  UniformResolvedComposition,
  type UniformPageParameters,
} from "@uniformdev/next-app-router";
import { resolveComponent } from "@/components/resolveComponent";

export default async function UniformPage(props: UniformPageParameters) {
  const { code } = await props.params;
  const result = requireComposition(await resolveRouteFromCode({ code }));

  await enhance({
    composition: result.route.compositionApiResponse.composition,
    enhancers: new EnhancerBuilder(),
    context: {},
  });

  return (
    <UniformContext result={result}>
      <UniformResolvedComposition result={result} resolveComponent={resolveComponent} />
    </UniformContext>
  );
}
```

The middleware makes no API calls, so you change the data only on the page.

## Use ordinary Next.js routes

You can render Uniform content in ordinary Next.js routes, for example `app/[locale]/[slug]/page.tsx`, and not in `app/uniform/[code]`. This is useful when an app has its pages in Next.js already, and Uniform manages only some regions of them.

The page must then make its own page state, because no code comes from the middleware.

### Step 1: Make the page state

`lib/uniform/getPageState.ts`

```ts
import {
  CANVAS_DRAFT_STATE,
  CANVAS_EDITOR_STATE,
  CANVAS_PUBLISHED_STATE,
  IN_CONTEXT_EDITOR_QUERY_STRING_PARAM,
} from "@uniformdev/canvas";
import { determinePreviewMode } from "@uniformdev/next-app-router/middleware";
import type { PageState } from "@uniformdev/next-app-router-shared";
import { draftMode } from "next/headers";

export type SearchParams = Promise<Record<string, string | string[] | undefined>>;

export async function getPageState(routePath: string, searchParams: SearchParams): Promise<PageState> {
  const common = { routePath, keys: undefined, defaultConsent: true, locale: undefined };

  // Published: do not read searchParams, so that the route stays static.
  if (!(await draftMode()).isEnabled) {
    return {
      ...common,
      compositionState: CANVAS_PUBLISHED_STATE,
      releaseId: undefined,
      previewMode: undefined,
      edgeMode: true, // only with the edge middleware; remove it for lite mode
    };
  }

  const params = new URLSearchParams();
  for (const [key, value] of Object.entries(await searchParams)) {
    for (const item of [value ?? []].flat()) params.append(key, item);
  }

  return {
    ...common,
    compositionState: params.has(IN_CONTEXT_EDITOR_QUERY_STRING_PARAM)
      ? CANVAS_EDITOR_STATE
      : CANVAS_DRAFT_STATE,
    releaseId: params.get("releaseId") ?? undefined,
    previewMode: determinePreviewMode({ searchParams: params, isDraftModeEnabled: true }),
  };
}
```

Set `defaultConsent` to the same value as your server configuration.

### Step 2: Resolve and render on the page

`app/[slug]/page.tsx`

```tsx
import {
  resolveRouteFromPath,
  UniformContext,
  UniformResolvedComposition,
} from "@uniformdev/next-app-router";
import { resolveComponent } from "@/components/resolveComponent";
import { getPageState, type SearchParams } from "@/lib/uniform/getPageState";

type Props = { params: Promise<{ slug: string }>; searchParams: SearchParams };

// Render each page on its first visit, then serve it from the cache.
// With Cache Components, return at least one value.
export const generateStaticParams = async () => [];

export default async function Page({ params, searchParams }: Props) {
  const { slug } = await params;
  const pageState = await getPageState(`/${slug}`, searchParams);
  const result = await resolveRouteFromPath({ path: pageState.routePath, pageState });

  return (
    <UniformContext result={result}>
      <h1>A page that Next.js owns</h1>
      <UniformResolvedComposition result={result} resolveComponent={resolveComponent} />
    </UniformContext>
  );
}
```

Uniform can have no composition for the path. Then `UniformResolvedComposition` renders nothing, and the Next.js part of the page still renders. To send a 404 or a Uniform redirect, call `requireComposition(result)`.

### Step 3: Configure the middleware and the preview

- **Middleware:** rewrite each request to its own URL, so that Next.js uses your route:

  `middleware.ts`

  ```ts
  import { handleUniformEdgeRoute } from "@uniformdev/next-app-router/edge";
  import { vercelManifestProvider } from "@uniformdev/next-app-router/vercel";
  import type { NextFetchEvent, NextRequest } from "next/server";

  // Make the provider one time, so that it keeps the manifest between requests.
  const manifest = vercelManifestProvider();

  export default function middleware(request: NextRequest, event: NextFetchEvent) {
    return handleUniformEdgeRoute({
      request,
      waitUntil: (promise) => event.waitUntil(promise),
      manifest,
      rewriteDestinationPath: async ({ source }) =>
        source === "route" ? `${request.nextUrl.pathname}${request.nextUrl.search}` : "", // an empty string keeps the default playground rewrite
    });
  }

  export const config = {
    matcher: [
      {
        source: "/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)",
        missing: [{ type: "header", key: "x-uniform-edge-origin" }],
      },
    ],
    runtime: "experimental-edge",
  };
  ```

  The edge middleware fetches your route and keeps the variants of the visitor. Without a `filter`, it processes all published pages. This agrees with `edgeMode: true` in `getPageState`. Do not add a `filter`, and do not use `vercelUniformEdgeMiddleware`. A page that the filter skips gets no transform, so its HTML shows all variants until hydration. For lite mode, use `handleUniformRoute` from `@uniformdev/next-app-router/middleware` with the same `rewriteDestinationPath`, and remove `edgeMode` from `getPageState`.
- **Preview:** if your URLs are different from the project map paths, change them in `resolveFullPath`. Refer to [Change the preview path](https://docs.uniform.app/docs/sdk/nextjs-app-router/preview#change-the-preview-path).
- **Routes:** each project map node that has a composition must have a Next.js route for its path. Without a route, the path gives a 404, also in Canvas preview.

## Composition cache

The composition cache gives components the full `ComponentInstance` data of the composition. For example, a navigation component can read the parameters of its child components:

`lib/compositionCache.ts`

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

export const compositionCache = createCompositionCache();
```

Give it to `UniformComposition` or `UniformResolvedComposition`:

```tsx
<UniformComposition code={code} resolveComponent={resolveComponent} compositionCache={compositionCache} />
```

Then read the data in a component:

`components/navigation.tsx`

```tsx
import type { ComponentProps } from "@uniformdev/next-app-router/component";
import { compositionCache } from "@/lib/compositionCache";

export const Navigation = ({ slots, context }: ComponentProps<unknown, "links">) => (
  <nav>
    {slots.links.items.map((item) => {
      if (!item) return null;
      const link = compositionCache.getUniformComponent({ compositionId: context._id, componentId: item._id });
      return (
        <a key={item._id} href={String(link?.parameters?.url?.value ?? "#")}>
          {String(link?.parameters?.title?.value ?? "")}
        </a>
      );
    })}
  </nav>
);
```

| Method | Description |
| --- | --- |
| `getUniformComposition({ id })` | Returns the root component of a composition. |
| `getUniformComponent({ compositionId, componentId })` | Returns one component of a composition. |
| `setUniformComposition(composition)` | Stores a composition. The renderer calls it. |

> **Known issue in 20.81.1-alpha.25:**
>
> In this version, all requests share one store, and the store uses only the composition ID as the key. A request can read the version of a composition that a different request stored, for example a draft or a different locale. The store is never cleared. Do not use the composition cache on a site where draft, locale or release versions of the same composition can render at the same time.

## Server context

`serverContext` keeps a value for one server request. Use it to give data to components deep in the tree without props:

`lib/requestLocale.ts`

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

export const [getRequestLocale, setRequestLocale] = serverContext<string>("en");
```

Set the value on the page before the components render. Read it in any server component of the same request. `serverContext` uses React `cache`, so it works only in server components.
