Next.js 13+ introduced the all-new App Router, which has significant differences from the traditional Pages Router. The App Router is built on React Server Components, providing more powerful features and better performance.
Key Differences
1. File Structure
Pages Router:
shellpages/ index.js about.js api/ users.js
App Router:
shellapp/ page.js about/ page.js api/ users/ route.js
2. Layout System
App Router's layout system is more powerful:
javascript// app/layout.js import './globals.css'; export default function RootLayout({ children }) { return ( <html lang="en"> <body> <header>Global Header</header> {children} <footer>Global Footer</footer> </body> </html> ); } // app/about/layout.js export default function AboutLayout({ children }) { return ( <div className="about-layout"> <aside>About Sidebar</aside> <main>{children}</main> </div> ); }
3. Data Fetching Methods
Pages Router:
javascript// pages/index.js export async function getServerSideProps() { const res = await fetch('https://api.example.com/data'); const data = await res.json(); return { props: { data } }; } export default function Home({ data }) { return <div>{data.title}</div>; }
App Router:
javascript// app/page.js async function getData() { const res = await fetch('https://api.example.com/data', { next: { revalidate: 3600 } }); return res.json(); } export default async function Page() { const data = await getData(); return <div>{data.title}</div>; }
4. Server Components vs Client Components
App Router uses Server Components by default:
javascript// app/page.js (Server Component - default) async function Page() { const data = await fetch('https://api.example.com/data').then(r => r.json()); return <div>{data.title}</div>; } export default Page; // app/components/Interactive.js (Client Component) 'use client'; import { useState } from 'react'; export default function Interactive() { const [count, setCount] = useState(0); return <button onClick={() => setCount(c => c + 1)}>{count}</button>; }
5. Route Parameters
Pages Router:
javascript// pages/posts/[id].js export async function getStaticPaths() { const posts = await getAllPosts(); return { paths: posts.map(post => ({ params: { id: post.id } })), fallback: 'blocking' }; } export async function getStaticProps({ params }) { const post = await getPost(params.id); return { props: { post } }; }
App Router:
javascript// app/posts/[id]/page.js export async function generateStaticParams() { const posts = await getAllPosts(); return posts.map(post => ({ id: post.id })); } export default async function PostPage({ params }) { const post = await getPost(params.id); return <article>{post.content}</article>; }
6. Loading States and Error Handling
App Router has built-in support:
javascript// app/posts/loading.js export default function Loading() { return <div>Loading...</div>; } // app/posts/error.js 'use client'; export default function Error({ error, reset }) { return ( <div> <h2>Something went wrong!</h2> <button onClick={() => reset()}>Try again</button> </div> ); } // app/posts/not-found.js export default function NotFound() { return <div>Post not found</div>; }
7. Server Actions
App Router exclusive feature:
javascript// app/actions.js 'use server'; import { revalidatePath } from 'next/cache'; export async function createPost(formData) { const title = formData.get('title'); const content = formData.get('content'); await savePost({ title, content }); revalidatePath('/posts'); } // app/posts/new/page.js import { createPost } from '../actions'; export default function NewPostPage() { return ( <form action={createPost}> <input name="title" /> <textarea name="content" /> <button type="submit">Create Post</button> </form> ); }
Migration Strategy
Progressive Migration
javascript// Can use both Routers in the same project // pages/ - Old code continues using Pages Router // app/ - New features use App Router // Link from App Router to Pages Router <Link href="/old-page">Old Page</Link>
Key Migration Steps
- Install Next.js 13+ and update configuration
- Create
app/directory structure - Migrate layouts to
app/layout.js - Gradually migrate pages to
app/directory - Update data fetching logic
- Add
'use client'directive to interactive components - Update API routes to Route Handlers
- Test and optimize performance
Performance Comparison
App Router Advantages:
- Smaller client bundle (Server Components not sent to client)
- Better SEO (default server-side rendering)
- More flexible caching strategies
- Built-in loading and error states
- Cleaner code structure
Pages Router Advantages:
- More mature and stable
- Richer ecosystem and documentation
- Simpler learning curve
- More third-party library support
Best Practices
- New Projects: Prioritize App Router
- Existing Projects: Progressive migration, start with simple pages
- Component Selection: Use Server Components by default, only use Client Components when interaction is needed
- Data Fetching: Leverage fetch API caching options
- State Management: Use Server Actions to replace some API routes
The App Router represents the future direction of Next.js, providing a more modern development experience and better performance.