~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@latestRestart ESLint in VS Code: Ctrl+Shift+P → "ESLint: Restart ESLint Server"
Step 2: Upgrade Core Components
- <Image>: Use
next/imageinstead ofnext/future/image - <Link>: No need to nest
<a>inside<Link> - <Script>: Move
beforeInteractivescripts toapp/layout.tsx - Fonts: Use
next/fontfor 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 Router | App Router |
|---|---|
| pages/index.js | app/page.js |
| pages/about.js | app/about/page.js |
| pages/blog/[slug].js | app/blog/[slug]/page.js |
Step 7: Migrate Data Fetching
getStaticProps→fetch(..., { cache: 'force-cache' })getServerSideProps→fetch(..., { cache: 'no-store' })getStaticPaths→generateStaticParams()
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
apptotailwind.config.jscontent 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 ./pagesWritten & researched by Dr. Shahin Siami