Knowledge Base/Next.js Page Router to App Router Migration Guide

Next.js Page Router to App Router Migration Guide

how-toDeveloperNextJS app routerNextJS page router

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 (as devDependency)
  • Other dependencies like nextreactreact-dom may 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:

  1. No need for manual context manifest downloads
  2. Context is managed through server components
  3. Use createServerUniformContext for server-side context
  4. Remove all references to:
    • @uniformdev/context
    • @uniformdev/context-next
    • @uniformdev/context-react
    • contextManifest.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

  1. Migrate any non-Uniform API routes to the new format of Next.js App Router.
  2. Update typescript, css and dev tools to the latest versions - depending on your needs.

Migration Checklist

  • [ ] Update package.json dependencies
  • [ ] Remove uniform:manifest and uniform:publish scripts
  • [ ] Create uniform.server.config.js
  • [ ] Update next.config.js with withUniformConfig
  • [ ] Move pages from pages/ to app/ 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

  1. Missing component/context props: App Router components require passing component and context to UniformText/UniformRichText
  2. Slot syntax: Remember to use slot={slots.name} instead of name="name"
  3. Component registration: Use resolver pattern instead of registerUniformComponent
  4. Preview routes: API routes must be in separate files (GET, POST, OPTIONS)
  5. Context manifest: Do NOT include manifest download commands - they're not needed!
Published: July 22, 2025