Upgrade from v1 to v2
This guide tells you how to move a Next.js App Router app from Uniform SDK v1 (@uniformdev/canvas-next-rsc) to v2 (@uniformdev/next-app-router), version 20.81.
warning
This guide is for apps that use the App Router with v1 of the SDK. If your app uses the Pages Router (@uniformdev/canvas-next), contact support.
Developer Preview
This guide moves an app to version 20.81 of @uniformdev/next-app-router. To use the developer preview (20.81.1-alpha.25.sha-f076f9b857), do the steps in this guide first. Then do the steps in Upgrade to the developer preview.
Overview of changes#
| Area | v1 | v2 |
|---|---|---|
| Packages | @uniformdev/canvas-next-rsc, -client and -shared | @uniformdev/next-app-router, -client and -shared |
| Next.js | 15.5.15 or later | 16.0.7 or later |
| Node.js | 18.18 or later | 20.9 or later |
| Middleware | None. The page resolves the route. | Required. The middleware resolves the route and rewrites the request. |
| Route file | app/[[...path]]/page.tsx | app/uniform/[code]/page.tsx |
| Playground | app/playground/page.tsx | app/playground/[code]/page.tsx |
| Client context | <UniformContext> in the root layout | The clientContextComponent prop of UniformComposition |
| Component props | Parameter values at the top level, and the full ComponentInstance | Parameters in parameters, as ComponentParameter<T> |
| Slots | <UniformSlot data={component} context={context} slot={slots.header} /> | <UniformSlot slot={slots.header} /> |
| Text | <UniformText component={component} context={context} parameterId="title" /> | <UniformText component={component} parameter={title} /> |
| Server configuration | uniform.server.config and withUniformConfig are required | The two are optional |
| Personalization and tests | evaluation in the server configuration | The middleware evaluates them. No configuration. |
Step 1: Update the packages#
Remove the v1 packages:
npm uninstall @uniformdev/canvas-next-rsc @uniformdev/canvas-next-rsc-client @uniformdev/canvas-next-rsc-sharedInstall v2, and Next.js 16:
npm install @uniformdev/next-app-router@^20.81.0 next@^16
@uniformdev/next-app-router-client and @uniformdev/next-app-router-shared install as dependencies of @uniformdev/next-app-router. Add them to package.json only when your code imports them.
note
Use the same version for all @uniformdev packages in package.json, for example @uniformdev/canvas and @uniformdev/context. Different versions can install two copies of @uniformdev/context.
v2 also depends on @uniformdev/canvas-react. Remove @uniformdev/canvas-react from package.json only if your code does not import it.
Step 2: Update the Next.js configuration#
v1 and v2 both have withUniformConfig. Change the import:
next.config.ts
In v2, withUniformConfig is optional. It connects uniform.server.config to the SDK when the file exists. Without the file, the SDK uses its default configuration.
Step 3: Update the server configuration#
v2 does not use most v1 options. Change uniform.server.config.ts as follows:
uniform.server.config.ts
| v1 option | v2 |
|---|---|
defaultConsent | defaultConsent |
context.disableDevTools | context.disableDevTools |
experimental.quirkSerialization | quirkSerialization (top level) |
canvasCache, manifestCache, projectMapCache | Removed. Use the cache option of the server clients in your own code. |
evaluation | Removed. The middleware evaluates personalizations and tests. |
ppr | Removed |
experimental.edgeRedirects, experimental.edgeCompositions, experimental.localeDynamicInputs | Removed |
The playgroundPath option of the preview handler | playgroundPath in the server configuration |
warning
Your file replaces the default configuration fully. The SDK does not merge the two. Thus always set these options in your file:
defaultConsent. Without it, the default consent isfalse.playgroundPath. Without it, the middleware does not send pattern previews to the playground route.
The default configuration of version 20.81 is defaultConsent: true, quirkSerialization: true, middlewareRuntimeCache: true and playgroundPath: "/uniform/playground". If your app uses the default playground route (app/uniform/playground/[code]) and these values, you can remove the file.
Step 4: Add the middleware#
v1 has no middleware. In v2, the middleware is required. It finds the route of each request, applies Uniform redirects, and rewrites the request to /uniform/[code].
Make middleware.ts in the project root:
middleware.ts
Keep middleware.ts on the edge runtime
Next.js 16 renames middleware.ts to proxy.ts, and shows a deprecation warning for middleware.ts. Do not rename the file. On Vercel, the SDK cannot read draft mode in proxy.ts (vercel/next.js#82344), and Canvas preview shows published content. Keep middleware.ts and runtime: "experimental-edge".
For the options of the middleware, refer to Middleware configuration.
Step 5: Replace the route file#
- Delete
app/[[...path]]/page.tsx. - Make
app/uniform/[code]/page.tsx.
Before (v1):
app/[[...path]]/page.tsx
After (v2):
app/uniform/[code]/page.tsx
These parts of v1 are removed:
| v1 | v2 |
|---|---|
retrieveRoute(props) | The middleware resolves the route. The page gets the code. |
PageParameters (params.path) | UniformPageParameters (params.code) |
mode="server" and mode="static" | Removed |
createServerUniformContext | Removed. To add quirks on the server, use the quirks option of the middleware. |
ContextUpdateTransfer with serverContext | Removed. @uniformdev/next-app-router-client has a client ContextUpdateTransfer that takes only update. |
retrieveCompositionBySlug, resolveComposition | Removed |
In version 20.81, the middleware applies Uniform redirects, and rewrites a missing route to /404. UniformComposition calls notFound() when the code has no composition.
Static params#
v1 got all paths from the project map. v2 takes a list of paths:
app/uniform/[code]/page.tsx
| v1 | v2 |
|---|---|
generateStaticParams, createStaticParams({ expand }) | createUniformStaticParams({ paths, rewrite, locale }) |
Returns { path: string[] } items | Returns { code: string } items |
For more information, refer to Static generation (ISR).
Step 6: Remove UniformContext from the root layout#
In v1, the root layout renders <UniformContext>. In v2, UniformComposition renders the Uniform Context for you.
Before (v1):
app/layout.tsx
After (v2):
app/layout.tsx
Give your client context component to UniformComposition (Step 5) and to UniformPlayground (Step 7).
Update the client context component#
The client context hooks have the same names in v2. Change the import to @uniformdev/next-app-router/component, and give compositionMetadata to useInitUniformContext:
uniform/clientContext.tsx
Step 7: Replace the playground page#
- Delete
app/playground/page.tsx. - Make
app/playground/[code]/page.tsx.
Before (v1):
app/playground/page.tsx
After (v2):
app/playground/[code]/page.tsx
The middleware sends pattern previews to ${playgroundPath}/[code]. This page is at /playground/[code], so playgroundPath must be "/playground" (Step 3).
Step 8: Update the preview route#
Move the playgroundPath option of the preview handler to the server configuration (Step 3), and change the import:
Before (v1):
app/api/preview/route.ts
After (v2):
app/api/preview/route.ts
In v2, the handler finds a pattern preview from the is_incontext_editing_playground query string, or from an id without a path. Use resolveFullPath only when your URLs are different from the project map paths.
Step 9: Update the component props#
v1 gives the parameter values at the top level of the props. v2 gives them in parameters, and wraps each value in ComponentParameter<T>.
Before (v1):
After (v2):
components/hero.tsx
The other props also changed:
| v1 prop | v2 prop |
|---|---|
| Parameter values at the top level | parameters, with ComponentParameter<T> values. Read the value in .value. |
component (the full ComponentInstance) | component (ComponentContext: _id, _parentId, slotName and slotIndex) |
slotName, slotIndex | component.slotName, component.slotIndex |
context (with composition, path, searchParams, isDraftMode and previewMode) | context (CompositionContext: _id, type, state, isContextualEditing, matchedRoute, dynamicInputs and pageState) |
| None | type and variant (string | undefined) |
For the full types, refer to the SDK reference.
UniformText and UniformRichText#
UniformText and UniformRichText take the parameter object, not its ID. They do not take context.
Before (v1):
After (v2):
The parameter prop is required, so render the component only when the parameter exists.
Step 10: Update the slots#
Slots were already in the slots prop in v1. In v2, UniformSlot does not take data and context.
Before (v1):
After (v2):
The render function of the children changed too. It gets _id in place of component:
| v1 | v2 |
|---|---|
({ child, component, key, slotName, slotIndex }) | ({ child, _id, key, slotName, slotIndex }) |
An empty slot renders null in v2, not an empty array. To get the items as an array, use getUniformSlot from @uniformdev/next-app-router/component.
Step 11: Update the clients#
v1 and v2 both have getRouteClient({ cache }). The getDefault* clients of v1 are removed:
| v1 | v2 |
|---|---|
getDefaultRouteClient({ searchParams }) | getRouteClient({ state }) or getRouteClient({ searchParams, draftModeEnabled }) |
getDefaultCompositionDeliveryClient({ searchParams }) | getCompositionDeliveryClient(…) with the same options |
getDefaultCanvasClient({ searchParams }) | getCompositionDeliveryClient(…). getCanvasClient is deprecated. |
getDefaultManifestClient({ searchParams }) | getManifestClient(…) with the same options |
getDefaultProjectMapClient({ searchParams }) | getProjectMapClient(…) with the same options. The options are required. |
getManifest({ searchParams }) | getManifest({ state }) or getManifest({ searchParams, draftModeEnabled }) |
In v2, searchParams is a URLSearchParams object, not a plain object. For more information, refer to Call Uniform APIs on the server.
These v1 helpers are removed: isDraftModeEnabled, isIncontextEditingEnabled, isDevelopmentEnvironment and isOnVercelPreviewEnvironment. Use draftMode() from next/headers to find draft mode.
Step 12: Use the adapter layer (optional)#
The adapter layer gives the parameter values at the top level of the props, as in v1. Use it to move your components to v2 one at a time.
Set up the adapter resolver#
uniform/resolve.tsx
The SDK finds a mapping by its type field, not by its key. Thus import * as mappings from the v1 starter works.
Adapt individual components#
Add mode: "adapted" to each mapping that you did not move to v2 yet:
uniform/mappings/page.tsx
An adapted component gets these props:
- The parameter values at the top level, as in v1.
component: the v2ComponentContextand theparametersobjects. It is not the v1ComponentInstance.type,variant,slotsandcontextfrom v2.
Thus change these parts of a v1 component, also in the adapted mode:
UniformSlot: import it from@uniformdev/next-app-router/component, and removedataandcontext.UniformText: import it from@uniformdev/next-app-router/compat. It takescomponentandparameterId, but notcontext.UniformRichText: there is no adapted version. Use the v2UniformRichTextwithparameter={component.parameters.description}.component.type,component.slotsandcomponent.variant: usetype,slotsandvariant.slotNameandslotIndex: usecomponent.slotNameandcomponent.slotIndex.context: use the v2 fields. The v1 fieldscomposition,path,searchParams,isDraftModeandpreviewModeare removed.
A type that has no mapping renders "Not implemented". The v1 DefaultNotImplementedComponent is removed.
Migration checklist#
- [ ] Remove
@uniformdev/canvas-next-rsc,-clientand-shared. Install@uniformdev/next-app-routerand Next.js 16. - [ ] Change the import of
withUniformConfigto@uniformdev/next-app-router/config. - [ ] Remove the v1 options from
uniform.server.config.ts. SetdefaultConsentandplaygroundPath. - [ ] Add
middleware.tswithuniformMiddleware()andruntime: "experimental-edge". - [ ] Replace
app/[[...path]]/page.tsxwithapp/uniform/[code]/page.tsx. - [ ] Replace
createStaticParamswithcreateUniformStaticParams. - [ ] Remove
<UniformContext>from the root layout. GiveclientContextComponenttoUniformCompositionandUniformPlayground. - [ ] Replace
app/playground/page.tsxwithapp/playground/[code]/page.tsx. - [ ] Remove the
playgroundPathoption from the preview handler. - [ ] Change the component props to
parametersandComponentParameter<T>, or use the adapter layer. - [ ] Change
UniformTextandUniformRichTextfromparameterIdtoparameter. - [ ] Remove
dataandcontextfromUniformSlot. - [ ] Replace the
getDefault*clients. - [ ] Do a test of the preview and visual editing in Canvas.
- [ ] Do a test of the personalizations and A/B tests.