Migrating from Pages Router to App Router in Next.js – A Gentle, Step-by-Step Guide for Modern Web Development

Next.js 13 introduced the App Router — a modern, powerful alternative to the classic Pages Router. This article walks you through a smooth migration process, including version upgrades, layout setup, component updates, data fetching, API routes, styling, and codemods. You’ll learn how to transition gradually while keeping your app stable and future-ready.

App RouterPages RouterNext.js MigrationServer ComponentsCodemods

~2 min read • Updated Dec 13, 2025

Why Migrate to App Router?


The App Router in Next.js 13+ offers:

  • Server Components: Less JavaScript sent to the client
  • Streaming: Faster page loads with React Suspense
  • Smart Caching: Built-in route and data caching
  • Simpler Data Fetching: No need for getStaticProps or getServerSideProps
  • Natural Layouts: Built-in layout support without custom wrappers

Golden Tip: You can use both routers together and migrate incrementally.


Step 1: Update Versions


npm install next@latest react@latest react-dom@latest
npm install -D eslint-config-next@latest

Restart ESLint in VS Code: Ctrl+Shift+P → "ESLint: Restart ESLint Server"


Step 2: Upgrade Core Components


  • <Image>: Use next/image instead of next/future/image
  • <Link>: No need to nest <a> inside <Link>
  • <Script>: Move beforeInteractive scripts to app/layout.tsx
  • Fonts: Use next/font for built-in font optimization

Step 3: Create the app Directory


project-root/
├── app/
├── pages/
└── ...

Step 4: Create Root Layout


// app/layout.tsx
export const metadata = {
  title: 'My Website',
  description: 'Built with love and Next.js',
}

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  )
}

This replaces _app.tsx and _document.tsx.


Step 5: Migrate next/head → Metadata API


// Before
<Head><title>Page Title</title></Head>

// After
export const metadata = { title: 'Page Title' }

Step 6: Migrate Pages to App Router


Pages RouterApp Router
pages/index.jsapp/page.js
pages/about.jsapp/about/page.js
pages/blog/[slug].jsapp/blog/[slug]/page.js

Step 7: Migrate Data Fetching


  • getStaticPropsfetch(..., { cache: 'force-cache' })
  • getServerSidePropsfetch(..., { cache: 'no-store' })
  • getStaticPathsgenerateStaticParams()

Step 8: Migrate API Routes


// app/api/hello/route.ts
export async function GET() {
  return Response.json({ message: 'Hello World!' })
}

Step 9: Migrate Routing Hooks


'use client'
import { usePathname, useSearchParams } from 'next/navigation'

const pathname = usePathname()
const searchParams = useSearchParams()

Step 10: Styling


  • Global CSS: Import in app/layout.tsx
  • Tailwind: Add app to tailwind.config.js content array

Step 11: Using Both Routers Together


Hard navigation between pages and app causes full reload. For soft navigation, use router.push() inside App Router.


Step 12: Use Codemods


npx next-codemod next-image-experimental ./pages
npx next-codemod new-link ./pages

Written & researched by Dr. Shahin Siami