Resolve compositions on a page
Developer preview
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:
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.
| 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#
- 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.compositionStatetells 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, andredirect()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
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
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
Set defaultConsent to the same value as your server configuration.
Step 2: Resolve and render on the page#
app/[slug]/page.tsx
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
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 withedgeMode: trueingetPageState. Do not add afilter, and do not usevercelUniformEdgeMiddleware. A page that the filter skips gets no transform, so its HTML shows all variants until hydration. For lite mode, usehandleUniformRoutefrom@uniformdev/next-app-router/middlewarewith the samerewriteDestinationPath, and removeedgeModefromgetPageState.Preview: if your URLs are different from the project map paths, change them in
resolveFullPath. Refer to 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
Give it to UniformComposition or UniformResolvedComposition:
Then read the data in a component:
components/navigation.tsx
| 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
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.