Next.js Page Router to App Router Migration Guide
Last updated: December 23, 2025
This guide helps developers transition their Uniform-powered Next.js applications from Page Router to App Router SDK. The migration involves switching to React Server Components (RSC) and updating package dependencies.
Benefits of Migration
Server-side first rendering and access to a plethora of App Router features
Better performance with React Server Components
Simplified SDK with fewer dependencies
Built-in streaming and progressive rendering and PPR-ready functionality
Improved developer experience with modern React patterns
Step 1: Package Dependencies
Remove these packages that are specific to Page Router
"@uniformdev/canvas-next": ""^19",
"@uniformdev/canvas-react": "^19",
"@uniformdev/context": "^19",
"@uniformdev/context-next": "^19",
"@uniformdev/context-react": "^19"Keep other Uniform packages
Depending on your existing state, you may have some non-Page Router specific that you will likely still need, but make sure to bump their versions to the latest.
"@uniformdev/project-map": "latest"
"@uniformdev/richtext": "latest"
"@uniformdev/assets": "latest"Add this package (specific to Next.js App Router)
"@uniformdev/canvas-next-rsc": "latest"
Keep these packages but update their versions
@uniformdev/cli(asdevDependency)Other dependencies like
next,react,react-dommay need to be updated to the latest versions as well.
Important: all @uniformdev packages must be of the same version.
Step 2: Perform Key Architecture Changes
1. Routing Structure
Page Router: pages/[[...slug]].tsx
App Router: app/[[...path]]/page.tsx
2. Package.json Scripts
Page Router:
{
"dev": "run-s uniform:manifest next:dev",
"build": "run-s uniform:manifest next:build",
"uniform:manifest": "uniform context manifest download --output ./lib/uniform/contextManifest.json",
"uniform:publish": "uniform context manifest publish"
}App Router:
{
"dev": "next dev",
"build": "next build"
}Important: Remove uniform:manifest and uniform:publish scripts - these are NOT needed for App Router!
3. Configuration Files
Add: uniform.server.config.js in project root:
/** @type {import('@uniformdev/canvas-next-rsc/config').UniformServerConfig} */
module.exports = {
defaultConsent: true,
evaluation: {
personalization: "hybrid",
}
};
Update: next.config.js:
const { withUniformConfig } = require("@uniformdev/canvas-next-rsc/config");
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
};
module.exports = withUniformConfig(nextConfig);
Step 3: Component Migration
1. Page Component
Page Router:
import { withUniformGetServerSideProps } from "@uniformdev/canvas-next/route";
import { UniformComposition } from "@uniformdev/canvas-react";
export const getServerSideProps = withUniformGetServerSideProps();
const page = ({ data }) => {
return <UniformComposition data={data} />;
};
App Router:
import {
UniformComposition,
PageParameters,
retrieveRoute,
} from "@uniformdev/canvas-next-rsc";
import { resolveComponent } from "@/uniform/resolve";
export default async function Page(props: PageParameters) {
const route = await retrieveRoute(props);
return (
<UniformComposition
{...props}
route={route}
resolveComponent={resolveComponent}
mode="server"
/>
);
}
2. Layout Component
Page Router (_app.tsx):
import { UniformContext } from "@uniformdev/context-react";
import { UniformAppProps } from "@uniformdev/context-next";
import createUniformContext from "lib/uniform/uniformContext";
import "../components/canvasComponents";
const clientContext = createUniformContext();
function MyApp({ Component, pageProps, serverUniformContext }: UniformAppProps) {
return (
<UniformContext context={serverUniformContext ?? clientContext}>
<Component {...pageProps} />
</UniformContext>
);
}
App Router (layout.tsx):
import { UniformContext } from "@uniformdev/canvas-next-rsc";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<main className="main">
<UniformContext>{children}</UniformContext>
</main>
</body>
</html>
);
}
3. Component Registration
Page Router:
import { registerUniformComponent } from "@uniformdev/canvas-react";
function Hero(props) {
return <div>{props.textParameter}</div>;
}
registerUniformComponent({
type: "hero",
component: Hero,
});
App Router:
import {
ComponentProps,
UniformText,
} from "@uniformdev/canvas-next-rsc/component";
type HeroParameters = {
textParameter?: string;
};
export const Hero = ({ component, context }: ComponentProps<HeroParameters>) => {
return (
<UniformText
component={component}
context={context}
parameterId="textParameter"
as="h1"
/>
);
};
// In resolve.tsx:
export const resolveComponent: ResolveComponentFunction = ({ component }) => {
if (component.type === "hero") {
return { component: Hero };
}
return { component: DefaultNotImplementedComponent };
};
4. Rendering Parameters
Page Router:
import { UniformText, UniformRichText } from "@uniformdev/canvas-react";
<UniformText parameterId="textParameter" placeholder="Enter text" />
<UniformRichText parameterId="richTextParameter" />
App Router:
import { UniformText, UniformRichText } from "@uniformdev/canvas-next-rsc/component";
<UniformText
component={component}
context={context}
parameterId="textParameter"
as="h1"
/>
<UniformRichText
component={component}
context={context}
parameterId="richTextParameter"
/>
5. Slots
Page Router:
import { UniformSlot } from "@uniformdev/canvas-react";
<UniformSlot name="content" />
App Router:
import { UniformSlot } from "@uniformdev/canvas-next-rsc/component";
<UniformSlot context={context} data={component} slot={slots.content} />
6. Preview Configuration
Page Router: pages/api/preview.ts
import { createPreviewHandler } from "@uniformdev/canvas-next";
export default createPreviewHandler({
secret: () => process.env.UNIFORM_PREVIEW_SECRET,
playgroundPath: "/playground",
});
App Router: app/api/preview/route.ts
import {
createPreviewGETRouteHandler,
createPreviewPOSTRouteHandler,
createPreviewOPTIONSRouteHandler,
} from "@uniformdev/canvas-next-rsc/handler";
export const GET = createPreviewGETRouteHandler({
playgroundPath: "/playground",
resolveFullPath: ({ path }) => (path ? path : "/playground"),
});
export const POST = createPreviewPOSTRouteHandler();
export const OPTIONS = createPreviewOPTIONSRouteHandler();
Step 4: Context and Personalization
Important: The App Router SDK handles context differently:
No need for manual context manifest downloads
Context is managed through server components
Use
createServerUniformContextfor server-side contextRemove all references to:
@uniformdev/context@uniformdev/context-next@uniformdev/context-reactcontextManifest.json
Uniform legacy code
Edge middleware
If you are using CDN-specific edge middleware to run edge personalization / Context, it is not necessary anymore, as it is natively supported by the SDK during Server-side rendering time.
Enhancers
If your codebase still uses Uniform enhancers, you can continue using this approach, even though it is discouraged as it adds to the SSR time if your enhancers are computationally intensive. Consider refactoring to the non-enhancer approach and using newer integrations that do not rely on this feature.
Theme Pack
If you use Component Starter Kit's theme pack, you can continue using it but the theme pack components wrapping the application may need to be updated. Newer version of Component Starter Kit is using another approach not compatible with Theme Pack (Design Extensions) and there is no upgrade path.
Other non-Uniform legacy code
Migrate any non-Uniform API routes to the new format of Next.js App Router.
Update typescript, css and dev tools to the latest versions - depending on your needs.
Migration Checklist
[ ] Update package.json dependencies
[ ] Remove
uniform:manifestanduniform:publishscripts[ ] Create
uniform.server.config.js[ ] Update
next.config.jswithwithUniformConfig[ ] Move pages from
pages/toapp/directory structure[ ] Convert component registration to resolver pattern
[ ] Update all UniformText/UniformRichText components to include component and context props
[ ] Update slot rendering to new syntax
[ ] Convert preview API route to App Router format
[ ] Remove context manifest references
[ ] Test preview functionality
[ ] Test personalization features
Common Issues
Missing component/context props: App Router components require passing
componentandcontextto UniformText/UniformRichTextSlot syntax: Remember to use
slot={slots.name}instead ofname="name"Component registration: Use resolver pattern instead of registerUniformComponent
Preview routes: API routes must be in separate files (GET, POST, OPTIONS)
Context manifest: Do NOT include manifest download commands - they're not needed!