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
| Concept | Pages Router | App Router |
|---|---|---|
| File convention | pages/index.tsx | app/page.tsx |
| Layout | _app.tsx wraps all pages | layout.tsx per segment |
| Data fetching | getServerSideProps / getStaticProps | async Server Components |
| Client components | All components are client by default | Server by default, 'use client' to opt in |
| Metadata | next/head in each page | metadata export or generateMetadata |
| API routes | pages/api/route.ts | app/api/route/route.ts |
| Middleware | middleware.ts (same) | middleware.ts (same) |
| Loading states | Manual | loading.tsx file convention |
| Error boundaries | Manual | error.tsx file convention |
| Streaming | Not supported | Built-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 --versionStep 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 workStep 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