Next.js App Router vs Pages Router: A Detailed Comparison Guide
Next.js App Router vs Pages Router: A Detailed Comparison Guide
// pages/about.js
export default function About() {
return <h1>About Page</h1>;
}
// pages/blog/[slug].js
export async function getStaticProps({ params }) {
const post = await getPost(params.slug);
return { props: { post } };
}
export async function getStaticPaths() {
const posts = await getAllPosts();
return {
paths: posts.map(p => ({ params: { slug: p.slug } })),
fallback: false
};
}
export default function BlogPost({ post }) {
return <article>{post.title}</article>;
}// app/about/page.tsx
export default function About() {
return <h1>About Page</h1>;
}
// app/blog/[slug]/page.tsx
export default async function BlogPost({ params }) {
const post = await getPost(params.slug);
return <article>{post.title}</article>;
}
// app/blog/[slug]/loading.tsx
export default function Loading() {
return <div>Loading post...</div>;
}
// app/blog/[slug]/error.tsx
'use client';
export default function Error({ error, reset }) {
return <button onClick={reset}>Try Again</button>;
}// app/layout.tsx (Root Layout)
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>
<nav>Global Nav</nav>
{children}
</body>
</html>
);
}
// app/dashboard/layout.tsx (Nested Layout)
export default function DashboardLayout({ children }) {
return (
<div className="dashboard">
<aside>Sidebar</aside>
<main>{children}</main>
</div>
);
}// App Router - much simpler
async function ProductPage({ params }) {
const product = await fetch(`/api/products/${params.id}`);
const data = await product.json();
return <div>{data.name}</div>;
}Next.js introduced the App Router in version 13, offering a completely new paradigm alongside the traditional Pages Router. This thread compares both approaches to help you choose the right one.
Pages Router (Traditional)
The Pages Router has been the default since Next.js was created. It uses file-based routing under the pages/ directory.
// pages/about.js
export default function About() {
return <h1>About Page</h1>;
}
// pages/blog/[slug].js
export async function getStaticProps({ params }) {
const post = await getPost(params.slug);
return { props: { post } };
}
export async function getStaticPaths() {
const posts = await getAllPosts();
return {
paths: posts.map(p => ({ params: { slug: p.slug } })),
fallback: false
};
}
export default function BlogPost({ post }) {
return <article>{post.title}</article>;
}// app/about/page.tsx
export default function About() {
return <h1>About Page</h1>;
}
// app/blog/[slug]/page.tsx
export default async function BlogPost({ params }) {
const post = await getPost(params.slug);
return <article>{post.title}</article>;
}
// app/blog/[slug]/loading.tsx
export default function Loading() {
return <div>Loading post...</div>;
}
// app/blog/[slug]/error.tsx
'use client';
export default function Error({ error, reset }) {
return <button onClick={reset}>Try Again</button>;
}// app/layout.tsx (Root Layout)
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>
<nav>Global Nav</nav>
{children}
</body>
</html>
);
}
// app/dashboard/layout.tsx (Nested Layout)
export default function DashboardLayout({ children }) {
return (
<div className="dashboard">
<aside>Sidebar</aside>
<main>{children}</main>
</div>
);
}// App Router - much simpler
async function ProductPage({ params }) {
const product = await fetch(`/api/products/${params.id}`);
const data = await product.json();
return <div>{data.name}</div>;
}