Upgrade to the developer preview

Developer preview

This feature is in developer preview. Use with caution as it may change unexpectedly. For more information, contact us.

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.

The upgrade has two parts:

  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. 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.

AreaVersion 20.81Developer preview
MiddlewareCalls the Route API, evaluates personalizations and tests, and applies redirects and 404sMakes no network calls in lite mode. It writes the request state into the code.
Cached pagesOne page for each combination of variantsOne page for each route
VariantsChosen in the middlewareChosen in the browser (lite mode) or at the edge (edge mode)
Redirects and 404sApplied in the middlewareApplied on the page by requireComposition
Change the composition dataA custom DataClientResolve the composition on the page, and change it there
Edge personalizationNot availablevercelUniformEdgeMiddleware 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.


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

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

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

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

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.
  • Keep the file as middleware.ts, with runtime: "experimental-edge". Do not rename it to proxy.ts.

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

app/uniform/[code]/page.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).

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

<UniformContext result={result} clientContextComponent={CustomUniformClientContext}> <UniformResolvedComposition result={result} resolveComponent={resolveComponent} /> <UniformResolvedComposition result={footer} resolveComponent={resolveComponent} /> </UniformContext>
  • Remove middlewareRuntimeCache.
  • Remove experimental.disableSwrMiddlewareCache.
  • Add your custom personalization selection algorithms to context.personalizationSelectionAlgorithms. Refer to Custom personalization algorithms.
  • Set defaultConsent in your file. Your file replaces the default configuration, so a missing defaultConsent is false.
Removed APIUse this
DataClient, DefaultDataClient, EnhanceRouteOptionsResolve the composition on the page, and change it before render. Refer to Change the composition data before render.
The dataClient option of the middleware, UniformComposition and resolveRouteFromCodeRemove it.
precomputeCompositionEdge mode (Part 2). The edge chooses the variants of each visitor.
expireMiddlewareCacheTagonRevalidateTags of createPreviewPOSTRouteHandler (Part 2).
pageState.components, pageState.rules, pageState.quirks, pageState.isPrefetch, pageState.requestPathNo replacement. The code no longer contains evaluation results. Read quirks in the browser with useQuirks.
result.code of ResolvedRouteResultNo replacement.
getRuleId, resolveComponentFromPageState, resolveRuleFromPageState, PageStateComponent, PageStateComponentFields (from @uniformdev/next-app-router-shared)No replacement.
The types GetRouteOptions, GetRouteFromMiddlewareOptions, GetRouteFromPageStateOptions, RewriteRouteOptions, RewriteRouteResultNo replacement.
ClientContextTestTransfer (from @uniformdev/next-app-router-client)No replacement.
  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.


Do these steps after Part 1. They use the Vercel edge middleware. For other hosts, refer to Set up edge mode on other hosts.

npm install @vercel/functions

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

middleware.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.

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

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

app/api/preview/route.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.

Make sure that a Uniform webhook sends these events to /api/preview: manifest.published, composition.*, entry.*, projectmap.node.* and redirect.*. Refer to Caching.

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

  1. Build and start the app:

    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:

    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:

    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.


  • 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.

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.