Launch pricing - early-adopter rates, prices increase June 9th. Shop now →

Guides8 min readby Minnie

Next.js Pages Router to App Router Migration Guide (2026)

A practical guide to migrating a Next.js project from Pages Router to App Router in 2026 - what changed, what breaks, what to migrate first, and what to leave alone.

Migrating from Next.js Pages Router to App Router is a significant but manageable project. The recommended approach is incremental - both routers can run in parallel in the same Next.js project, so you migrate route by route rather than all at once. Here is the complete migration guide for 2026.

Next.js App Router was introduced in Next.js 13.4 and became stable and recommended in Next.js 14. By 2026, nearly all new Next.js projects use App Router. If you're on Pages Router, here's everything you need to know about migrating.

Key differences between Pages Router and App Router

ConceptPages RouterApp Router
File conventionpages/index.tsxapp/page.tsx
Layout_app.tsx wraps all pageslayout.tsx per segment
Data fetchinggetServerSideProps / getStaticPropsasync Server Components
Client componentsAll components are client by defaultServer by default, 'use client' to opt in
Metadatanext/head in each pagemetadata export or generateMetadata
API routespages/api/route.tsapp/api/route/route.ts
Middlewaremiddleware.ts (same)middleware.ts (same)
Loading statesManualloading.tsx file convention
Error boundariesManualerror.tsx file convention
StreamingNot supportedBuilt-in with Suspense

Step 1: Upgrade Next.js to the latest version

npm install next@latest react@latest react-dom@latest

# Check your current version first
npx next --version

Step 2: Create the app directory alongside pages

The Pages Router and App Router can coexist. Create an `app` directory at your project root. Next.js will serve routes from `app` when available, falling back to `pages` for anything not yet migrated.

# Your project structure during migration
/app
  layout.tsx      # Root layout (required)
  page.tsx        # Replaces pages/index.tsx (migrate when ready)
/pages
  index.tsx       # Still works until you remove it
  about.tsx       # Unmigrated pages keep working
  api/
    auth.ts       # API routes in pages/api still work

Step 3: Create the root layout

// app/layout.tsx - required, replaces pages/_app.tsx and pages/_document.tsx
import type { Metadata } from "next";
import "./globals.css";

export const metadata: Metadata = {
  title: { default: "My App", template: "%s | My App" },
  description: "My app description",
};

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en" suppressHydrationWarning>
      <head>
        {/* Flash-free dark mode script */}
        <script dangerouslySetInnerHTML={{ __html: `
          (function() {
            var s = localStorage.getItem('theme');
            var sys = matchMedia('(prefers-color-scheme: dark)').matches;
            if (s === 'dark' || (!s && sys)) document.documentElement.classList.add('dark');
          })();
        `}} />
      </head>
      <body className="bg-background text-foreground">
        {children}
      </body>
    </html>
  );
}

Step 4: Migrate pages one at a time

Start with the simplest pages - those with no data fetching. Create the corresponding file in app, convert the component to a Server Component by default, and remove the old file from pages when done.

// Before: pages/about.tsx (Pages Router)
export default function AboutPage() {
  return <div>About page</div>;
}

// After: app/about/page.tsx (App Router)
// No change needed for simple pages - Server Component by default
export default function AboutPage() {
  return <div>About page</div>;
}

// Metadata replaces next/head
export const metadata = {
  title: "About",
};

Step 5: Migrate data fetching

// Before: pages/posts/[id].tsx (Pages Router)
export async function getServerSideProps({ params }) {
  const post = await fetchPost(params.id);
  return { props: { post } };
}
export default function PostPage({ post }) {
  return <div>{post.title}</div>;
}

// After: app/posts/[id]/page.tsx (App Router)
// The component itself is async - no wrapper function needed
export default async function PostPage({
  params,
}: {
  params: { id: string };
}) {
  const post = await fetchPost(params.id);  // runs on the server
  return <div>{post.title}</div>;
}

Common migration issues

  • useState/useEffect in Server Components - add 'use client' directive to components that use React hooks or browser APIs
  • Context providers - wrap them in a client component and import into layout.tsx
  • next/router vs next/navigation - App Router uses useRouter from next/navigation, not next/router. The APIs are different.
  • getStaticPaths → generateStaticParams - the function name and return format changed
  • API routes location - move from pages/api/route.ts to app/api/route/route.ts with named exports (GET, POST, etc.)
  • next/head → metadata export - head tags are now handled by exporting a metadata object from page.tsx

What to migrate last

Migrate the most complex pages last: authentication flows, pages with complex data fetching, and pages that use many client-side libraries. These require more careful handling of the server/client boundary. Simple marketing pages and static content should be migrated first.

Frequently asked questions

Should I migrate from Next.js Pages Router to App Router in 2026?

For active projects, yes - App Router has better performance through React Server Components, built-in streaming, and simpler data fetching. For stable projects that aren't being actively developed, the migration cost may not be worth the benefit. New projects should always start with App Router.

Can Pages Router and App Router run together in the same Next.js project?

Yes. This is the recommended migration strategy. Both routers work simultaneously - routes in app/ take precedence over the same routes in pages/. You can migrate incrementally, one route at a time, without a big-bang rewrite.

TheKitBase templates are built exclusively on Next.js App Router - no legacy patterns, no migration needed. Start fresh with a production-ready App Router template from $39.

Browse Templates