Next.js App Router SDK
Version 2 of the Next.js App Router SDK
This is v2 of the Uniform SDK for Next.js App Router. It requires Next.js 16 and works only with the App Router.
- Using Next.js 15 with the Page Router? You can continue using the Next.js Page Router SDK, which remains fully supported.
- Using Next.js 15 with the App Router (earlier SDK)? You can upgrade to this new SDK. See the upgrade guide for step-by-step instructions.
- Planning to migrate from Page Router to App Router? If you are upgrading your Next.js project from Page Router to App Router, you can also adopt this new SDK. Contact support for help with this transition.
The Uniform SDK for Next.js App Router (@uniformdev/next-app-router) is purpose-built for Next.js 16 and provides a first-class integration between Uniform's DXP and the App Router. It handles route resolution, component rendering, personalization, A/B testing, and visual editing -- all optimized for React Server Components and edge middleware.
Why Next.js 16#
This SDK takes full advantage of Next.js 16 capabilities, most notably the cacheComponents support (learn more here on how to enable it). When enabled, route resolution results are cached at the framework level, which delivers:
- Static-like performance -- Cached compositions are served instantly without round-trips to the Uniform API on subsequent requests, achieving response times comparable to fully static sites.
- Flicker-free personalization -- Personalization and A/B test variants are resolved at the edge in middleware before the page renders. The visitor always receives the correct variant on the first paint -- no client-side swap, no layout shift, no flicker.
- Edge-evaluated A/B testing -- Tests are evaluated in middleware and the winning variant is baked into the serialized page state. The page component simply renders the result, with no client-side JavaScript required for test selection.
- Automatic cache invalidation -- The SDK integrates with Next.js cache tags so that publishing changes in Uniform automatically invalidates only the affected cached pages.
See Enabling cacheComponents below for setup instructions.
How it works#
Understanding the request lifecycle helps you reason about configuration choices and debug issues effectively.
Middleware intercepts the request -- When a visitor requests a page (e.g.,
/about), the Uniform middleware resolves the route through the Uniform Route API, evaluates personalization and A/B tests at the edge, and rewrites the request to/uniform/[code]wherecodeis a serialized page state.The page component receives the code -- The
app/uniform/[code]/page.tsxroute handler receives the encoded page state, resolves the full composition data, and renders the component tree.Components render server-side -- All Uniform components are React Server Components by default. Client interactivity (quirks, scores, context updates) is added only where needed.
Visual editing works out of the box -- When previewing in the Uniform dashboard, the SDK automatically enables inline editing, contextual editing attributes, and live preview capabilities.
Getting started#
Prerequisites#
- Next.js 16+ (App Router)
- Node.js 22+
- A Uniform project with compositions set up
Quick start options#
The fastest way to get up and running is to start from a working project rather than integrating from scratch.
Run thisin your favorite terminal and select Next.js:
This scaffolds a new project with all required content and files pre-configured.
You will have two options:
Component Starter Kit -- A feature-rich starter with pre-built components, design tokens, TailwindCSS theming, and more.
You can also clone it manually from uniformdev/component-starter-kit-next-approuter and follow the repo readme to setup manually.
Hello World -- A minimal, fully functional reference app showing the essential Uniform + App Router integration.
You can also clone it from uniformdev/examples/nextjs-app-router-v2.
Alternatively, if you prefer to add Uniform to an existing project manually, follow the steps below.
Install packages#
This is the only required package. It includes the server-side SDK, component utilities, middleware, config helpers, and preview handlers.
For advanced client-side context customization, you may also need:
Environment variables#
Create a .env file in your project root:
note
You can find these values in your Uniform project under Settings > API Keys. See the API keys guide for more information.
Project setup#
The Uniform SDK requires a specific set of files to wire up routing, rendering, and preview. Here is the minimal file structure:
The following sections walk through each file.
Step 1: Next.js configuration#
Wrap your Next.js configuration with withUniformConfig. This sets up the necessary aliases for the Uniform server config:
withUniformConfig automatically detects whether a uniform.server.config.ts file exists in your project root and sets up Webpack/Turbopack aliases accordingly. If no config file is found, default settings are used.
Step 2: Middleware#
Create middleware.ts at the root of your project. The middleware is required -- it intercepts incoming requests, resolves routes through the Uniform Route API, evaluates personalization rules at the edge, and rewrites requests to the composition page handler.
warning
This guide is for customers who are already using the App Router with the earlier Uniform SDK packages (@uniformdev/canvas-next-rsc, @uniformdev/canvas-react, etc.) and want to upgrade to the v2 SDK. If you are migrating from the Page Router to the App Router, contact support for assistance.
Next.js 16 users: To support Uniform preview for apps deployed to Vercel, the middleware file must be named middleware.ts and you should add runtime: "experimental-edge" to your config export:
Step 3: Main composition route#
Create app/uniform/[code]/page.tsx. This is the route that the middleware rewrites requests to after resolving the composition:
Key points about UniformComposition:
- It is an async server component that fetches and renders the full composition tree.
- It internally handles
UniformContextsetup (wrapped inSuspense), so you do not placeUniformContextinlayout.tsx. - If the route cannot be resolved, it automatically calls
notFound(). - You can optionally add
generateStaticParamsfor build-time pre-rendering -- see Static generation (ISR) below.
Step 4: Playground route#
Create app/playground/[code]/page.tsx. The playground route enables visual editing and previewing individual compositions from the Uniform dashboard:
Step 5: Preview handler and ISR handler#
Create app/api/preview/route.ts to handle both preview requests (GET handler) and ISR requests (POST handler) from Uniform:
Step 6: Component resolution#
Create components/resolveComponent.ts. This function maps Uniform component types (defined in your Uniform project) to React components:
The resolveComponent function receives the raw ComponentInstance from the Uniform canvas data and returns a ResolveComponentResult containing the React component to render. It is called for every component in the composition tree.
Building components#
All Uniform components receive standardized props through the ComponentProps type. Components are React Server Components by default -- only add "use client" when you need browser APIs or interactivity.
Component props#
Every component receives these props via ComponentProps<TParameters, TSlotNames>:
| Prop | Type | Description |
|---|---|---|
type | string | The component type identifier (e.g., "hero") |
variant | string | undefined | Active variant ID when inside a personalization or test |
parameters | TParameters | The component's parameter values, each wrapped in ComponentParameter<T> |
slots | Record<TSlotNames, SlotDefinition> | Child component slots |
component | ComponentContext | Component metadata (_id, _parentId, slotName, slotIndex) |
context | CompositionContext | Composition-level metadata (_id, type, state, isContextualEditing, matchedRoute, dynamicInputs) |
Basic component with parameters#
Parameters in Uniform are typed values managed through the Uniform dashboard. In your components, each parameter must be typed with ComponentParameter<T>:
warning
Always mark parameters as optional using ?. Even if a parameter is marked as required in the Uniform component definition, it can be undefined at runtime (e.g., when a new component is first added to a composition and hasn't been filled in yet).
Accessing raw parameter values#
When you need to read a parameter value directly (for example, for non-visible values like image alt text, URLs, or conditional logic), access the .value property:
UniformText#
UniformText renders text parameters with built-in support for inline editing in the Uniform visual editor:
UniformText supports conditional parameter values driven by quirks. It automatically evaluates quirk-based conditions and renders the matching value.
UniformRichText#
UniformRichText renders rich text parameters with full formatting support:
Slots#
Slots define where child components can be placed within a parent component. A page component typically has slots like header, content, and footer.
Rendering slots with UniformSlot#
Custom slot rendering#
UniformSlot accepts a children render function for wrapping individual slot items:
Rendering slot items directly#
You can iterate over slot items directly for full control over rendering:
getUniformSlot utility#
The getUniformSlot function extracts slot items as an array of ReactNode:
This is useful when you need to count items, conditionally render, or apply array operations before rendering.
Server configuration#
The optional uniform.server.config.ts file (placed in your project root) lets you configure caching, consent defaults, and experimental features:
If this file does not exist, the SDK uses sensible defaults. The withUniformConfig wrapper in next.config.ts automatically picks up this file.
Middleware configuration#
The middleware is the most critical part of the Uniform integration. It runs at the edge and handles route resolution, personalization evaluation, and request rewriting.
Basic middleware#
The simplest setup uses uniformMiddleware() with no options:
Using handleUniformRoute directly#
For more control, use handleUniformRoute directly:
Middleware options#
| Option | Type | Description |
|---|---|---|
rewriteRequestPath | (options) => Promise<RewriteRequestPathResult> | Transform the incoming request path before route resolution |
rewriteDestinationPath | (options) => Promise<string> | Transform the output path after resolution |
pathPatternsWithVariations | string[] | Paths that should pre-compute personalizations |
release | { id: string } | Content release ID to resolve against |
quirks | Quirks | Custom quirks to inject into the visitor context |
defaultConsent | boolean | Override the default consent setting |
locale | string | Locale to use for route resolution |
Setting quirks in middleware#
Inject custom quirks based on request data:
Locale handling in middleware#
For localized sites, use rewriteRequestPath to prepend locale information:
Specifying locale directly#
If you determine the locale from cookies or headers:
Custom route mapping#
Use findRouteMatch to map dynamic URL patterns to Uniform project map nodes:
The keys object passes dynamic URL segments as dynamic inputs to the composition. These are accessible in the component via context.dynamicInputs.
Adding URL query string values to page state#
Query string values, and other custom values, can be read in middleware and passed through the keys object to make them available server-side while rendering the page. These values will be accessible in your component via context.pageState.keys. It is recommended to only include keys that are needed to render the page.
Moving the uniform page to a different location#
If you want to place the uniform catch-all route at a different path (e.g., under a locale segment):
You would then place your page at app/[locale]/uniform/[code]/page.tsx.
Scoping middleware to specific paths#
Use Next.js middleware config.matcher to limit which paths Uniform handles:
Override default consent per request#
Content release support#
Switch to a specific content release by passing the release ID through middleware:
Personalization and testing#
Personalization and A/B testing are configured in the Uniform dashboard and evaluated automatically at the edge by the middleware. No additional code is required in your components -- the SDK renders the winning variant transparently.
Server-side precomputation#
If you want to evaluate tests and personalizations server-side (for fully static output), use precomputeComposition:
precomputeComposition walks the composition tree and replaces personalization and test containers with their resolved variants. You can selectively control which tests and personalizations to evaluate:
Vercel geo-IP quirks#
When deployed on Vercel, the middleware automatically populates quirks from Vercel's geo-IP headers:
| Vercel Header | Quirk Key |
|---|---|
x-vercel-ip-country | vc-country |
x-vercel-ip-country-region | vc-region |
x-vercel-ip-city | vc-city |
These quirks are available for personalization rules without any additional configuration.
Client-side context#
Most Uniform components are server-rendered and don't need client-side JavaScript. However, certain features require client-side interactivity:
- Reading or updating visitor quirks
- Tracking visitor scores
- Responding to context changes in real-time
warning
Any component that uses client-side hooks (useUniformContext, useQuirks, useScores) or browser APIs must be marked with "use client" at the top of the file. This is a React Server Components requirement, not specific to Uniform.
Push client components as far down the component tree as possible. Adding "use client" to a component turns it and all of its children into client components, which means more JavaScript shipped to the browser and no server-side rendering for that subtree. Instead of making an entire page component a client component, extract the interactive piece (e.g., a button that updates a quirk) into its own small client component and embed it within the server-rendered parent.
useUniformContext#
Access the Uniform Context instance on the client:
note
The context object may be undefined initially while the client-side context initializes. Always check for undefined before using it.
useQuirks#
Provides reactive access to the current visitor's quirk values. The component re-renders whenever quirks change:
useScores#
Provides reactive access to the current visitor's score values:
Custom client context#
For advanced scenarios like custom Context plugins, analytics integrations, or custom dev tools behavior, create a custom client context component:
Pass it to UniformComposition:
Enhancing composition data#
To enhance composition data (addjust content or structure, fetch additional data, etc.), you need to create a custom data client and configure it in two places: the middleware handler and the UniformComposition component.
First, create a custom data client. It's recommended to put this in a central location (e.g., lib/dataClient.ts) as you'll need to use the same instance in both places:
Pass an instance to the middleware handler:
Also pass the same data client instance to UniformComposition:
Composition cache#
The composition cache provides server-side access to the full ComponentInstance data within any component. This is useful when you need to access composition-level content (metadata parameters, page title, for example), or inspect the whole slot contents instead of just rendering slot components.
Setting up the cache#
Pass the cache to UniformComposition:
Accessing component data#
Within any component, use the cache to retrieve the full ComponentInstance for a slot child:
The cache exposes three methods:
| Method | Description |
|---|---|
getUniformComposition({ id }) | Get the full root composition by ID |
setUniformComposition(composition) | Store a composition (called internally) |
getUniformComponent({ compositionId, componentId }) | Get a specific component instance by ID |
Enabling Cache Components#
Next.js 16 introduces the notion of Cache Components, which lets the framework cache the result of server-side function calls. The Uniform SDK ships a cache-enabled version of resolveRouteFromCode that wraps route resolution in 'use cache', so subsequent requests for the same page state are served from cache without additional API calls.
Step 1: Enable cacheComponents in Next.js config#
Step 2: Import resolveRouteFromCode from the cache path#
In your app/uniform/[code]/page.tsx, change the import path:
The rest of the page component remains identical -- only the import path changes. The cache-enabled resolveRouteFromCode internally uses 'use cache' so the composition data is cached at the framework level and served on subsequent requests without re-fetching from the Uniform API.
note
In development mode, caching is automatically disabled so you always see fresh data. Cache is also bypassed when draft mode or in-context editing is active.
Suspense and streaming#
You can wrap individual components in Suspense boundaries via the resolveComponent function. This enables streaming: the page shell renders immediately while slower components load in asynchronously.
When suspense is provided in the resolve result, the SDK wraps the component in a React <Suspense> boundary with the specified fallback. This is particularly useful for components that fetch additional data or perform async operations during server rendering.
Server clients#
The SDK provides server-only clients for accessing Uniform data directly:
Examples#
These clients are server-only (they import 'server-only') and cannot be used in client components.
Adapter compatibility mode#
If you're migrating from an older Uniform SDK or prefer a flattened props API where parameter values are spread directly onto component props (instead of nested under parameters), use the adapter compatibility layer:
In adapted mode, parameter values are extracted from ComponentParameter wrappers and spread directly onto the component props. The original parameters remain accessible via component.parameters.
UniformText and UniformSlot are also exported from @uniformdev/next-app-router/compat.
Manifest management#
Next.js App Router does not require downloading (pulling) the manifest locally. The manifest is fetched at runtime by the SDK.
The approach of pulling manifest into a local file and bundling it with the application codebase was the default approach with previous versions of Next.js SDK and is not recommended now.
In terms of manifest publishing, which you need to do when you sync manifest definitions to your project, you can optionally enable mamifest publishing from your command line by adding this script to your package.json: