Next.js pages can live in the modern app/ directory or the legacy pages/ directory. Knowing how both work helps you maintain older codebases while writing new features with React Server Components.
Before Next.js 13, every route lived inside pages/ and rendered as a Client Component unless you exported special server functions. In the modern App Router, every page.tsx is an async Server Component by default.
| Feature | Legacy Pages Router (pages/) |
Modern App Router (app/) |
|---|---|---|
| Entry File | pages/index.tsx |
app/page.tsx |
| Default Component | Client Component | Server Component |
| Data Fetching | getServerSideProps / getStaticProps |
Async component with fetch() |
| Shared Shell | pages/_app.tsx |
Nested layout.tsx files |
In the pages/ directory, server data fetching requires exporting getServerSideProps alongside the page component. The server serializes props and sends them to the client bundle.
// pages/products/[id].tsx (Legacy Pages Router)
import type { GetServerSideProps } from 'next';
interface Product {
id: string;
name: string;
price: number;
}
export const getServerSideProps: GetServerSideProps = async (context) => {
const id = context.params?.id;
const res = await fetch(`https://api.example.com/products/${id}`);
const product: Product = await res.json();
return { props: { product } };
};
export default function ProductPage({ product }: { product: Product }) {
return (
<section className="p-6">
<h2 className="text-2xl font-bold">{product.name}</h2>
<p className="mt-2">${product.price}</p>
</section>
);
}
In app/, you fetch data directly inside the page function using async and await. No separate getServerSideProps wrapper is needed.
// app/products/[id]/page.tsx (Modern App Router)
interface PageProps {
params: Promise<{ id: string }>;
}
export default async function ProductPage({ params }: PageProps) {
const { id } = await params;
const res = await fetch(`https://api.example.com/products/${id}`, {
cache: 'no-store',
});
const product = await res.json();
return (
<section className="p-6">
<h2 className="text-2xl font-bold">{product.name}</h2>
<p className="mt-2">${product.price}</p>
</section>
);
}
Next.js supports both pages/ and app/ inside the same project during a migration. Keep working routes in pages/, build new routes in app/, and move legacy pages over one route at a time.
pages/ and app/: Having pages/about.tsx and app/about/page.tsx causes a build conflict.next/router inside app/: App Router pages must use next/navigation instead of next/router.No, start all new Next.js projects with the App Router (app/). It offers smaller client bundles, nested layouts, and Server Actions.
pages/ and app/ run together in one app?Yes, Next.js allows both directories to coexist as long as they do not resolve to the exact same URL path.
_app.tsx and _document.tsx in App Router?A single root app/layout.tsx file replaces both _app.tsx and _document.tsx by defining <html>, <body>, and global styles.
Sign in to track your learning journey, earn industry-recognized certificates, and join our elite developer community.
Quick Access With