
Next.js has two routers. The Pages Router (the pages/ folder) is the original one, used by most tutorials and older codebases. The App Router (the app/ folder) came in Next.js 13 and is now the recommended choice for new projects. Both are still supported, and both can live in the same project.
If you work with Next.js or are preparing for a frontend interview, someone will ask you how they differ. This post compares them side by side: routing files, layouts, data fetching, rendering, metadata, API endpoints and navigation. It ends with a practical migration plan.
1. The short answer
| Topic | Pages Router (pages/) | App Router (app/) |
|---|---|---|
| Default component type | Client component (all code ships to the browser) | Server Component (no JS shipped unless you add 'use client') |
| Route definition | Each file is a route | Each folder is a route segment, page.tsx makes it public |
| Layouts | One global _app, per-page layout patterns by hand | Nested layout.tsx files that persist between navigations |
| Data fetching | getServerSideProps, getStaticProps, getStaticPaths | async components that await data directly, plus generateStaticParams |
| Loading and errors | Manual state in each page | loading.tsx (Suspense) and error.tsx (error boundary) per segment |
| Streaming | Not supported | Built in through Suspense |
| Mutations | API routes called from the client | Server Actions, or Route Handlers |
| SEO tags | next/head | metadata export or generateMetadata |
| Router hooks | next/router | next/navigation |
2. Routing and special files
In the Pages Router, the file path is the URL. pages/blog/[slug].tsx serves /blog/:slug. Every file inside pages/ becomes a route, so helpers and components have to live somewhere else.
In the App Router, folders define the URL, and only a few special file names mean anything. So you can keep components, tests and styles right next to the route that uses them, and they don't become public.

The special files in app/ you should know:
page.tsx: the UI for a route. Without it, the folder is not a public URL.layout.tsx: UI that wraps the segment and everything under it. It keeps its state across navigations.loading.tsx: an instant loading state. Next.js wraps it in Suspense for you.error.tsx: an error boundary for the segment (must be a client component).not-found.tsx: shown when you callnotFound().route.ts: an HTTP endpoint (replacespages/api).template.tsx: like a layout, but remounts on every navigation.
Some folder names give you extra control. (group) folders organise routes without changing the URL. _private folders are ignored by routing. @slot folders turn on parallel routes.
3. Layouts and shared UI
Say you want a dashboard sidebar that stays mounted while you move between pages. In the Pages Router you need a workaround like the getLayout pattern in _app. In the App Router it is just a file:
// app/dashboard/layout.tsx
export default function DashboardLayout({ children }: { children: React.ReactNode }) {
return (
<div className="flex">
<Sidebar />
<main>{children}</main>
</div>
);
}
Layouts nest on their own. app/layout.tsx wraps app/dashboard/layout.tsx, which wraps every page under /dashboard. On navigation only the segment that changed re-renders, so the sidebar keeps its scroll position and state.
4. Data fetching
Pages Router
You fetch data in special functions that run on the server and pass props to the page:
// pages/products/[id].tsx
export async function getServerSideProps({ params }) {
const product = await getProduct(params.id);
return { props: { product } };
}
export default function ProductPage({ product }) {
return <h1>{product.name}</h1>;
}
getStaticProps with getStaticPaths builds pages at build time. Add revalidate and you get Incremental Static Regeneration (ISR). These functions only work in page files. So a deeply nested component that needs data has to get it through props.
App Router
Any Server Component can be async and fetch its own data, no matter where it sits in the tree:
// app/products/[id]/page.tsx
export default async function ProductPage({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params;
const product = await getProduct(id); // runs on the server
return <h1>{product.name}</h1>;
}
export async function generateStaticParams() {
const products = await getAllProducts();
return products.map(p => ({ id: p.id }));
}
In recent Next.js versions, params and searchParams are Promises, so you await them. You control caching per request or per segment (for example export const revalidate = 60). Newer versions also add the 'use cache' directive for explicit caching. Caching defaults have changed between major versions, so check the docs for your version instead of trusting old blog posts.
5. Server and Client Components
This is the biggest change in thinking. In the Pages Router, every component is hydrated in the browser, even plain static markup. In the App Router, components are Server Components by default. They render on the server, can read databases and secrets directly, and send no JavaScript to the browser. When a file needs state, effects, event handlers or browser APIs, you add 'use client' at the top to make it interactive.
A good rule: keep pages and layouts as Server Components, and push 'use client' down to the smallest interactive pieces (a like button, a search box, a modal). Our React Server Components guide explains the boundary in detail.
6. Metadata, API routes and navigation
- Metadata: instead of
<Head>, export ametadataobject. If the title depends on data, export an asyncgenerateMetadatafunction. Next.js merges metadata from nested layouts and removes duplicates. - API endpoints:
pages/api/hello.tsexports one handler.app/api/hello/route.tsexports one function for each HTTP method (GET,POSTand so on), using the standardRequestandResponseobjects. - Mutations: you can call Server Actions (functions marked
'use server') from forms and client components, with no API route to write. - Navigation hooks: import
useRouter,usePathnameanduseSearchParamsfromnext/navigation, notnext/router. The newuseRouterhas noqueryorpathnamefields.
7. How to migrate step by step
You don't need to rewrite everything in one go. Both routers work side by side, so you can move one route at a time. Just make sure a route doesn't exist in both folders.
- Upgrade Next.js and React first, and fix any warnings while you are still on the Pages Router.
- Create
app/layout.tsxwith the<html>and<body>tags. Move global CSS and providers from_appand_documentinto it. Providers that use context go in a small client component. - Move a simple, low-risk page first, like an About page. Create
app/about/page.tsxand deletepages/about.tsx. - Replace data functions:
getServerSidePropsorgetStaticPropsbecomes anawaitinside the async page, andgetStaticPathsbecomesgenerateStaticParams. - Mark interactive components with
'use client'. Any component that usesuseState,useEffectoronClickneeds it. - Swap imports:
next/routertonext/navigation,next/headto the metadata API. - Move API routes to
route.tshandlers when it suits you.pages/apikeeps working until then. - Measure bundle size and Core Web Vitals before and after each step.
Which should you choose?
For a new project, use the App Router. That is where new Next.js features land. For a large, stable Pages Router app, there is no hurry to migrate everything. Move a route when you are already changing it, and start with routes that gain the most from layouts, streaming or smaller client bundles.
- Pages Router: files are routes, everything is a client component, data comes from page-level functions.
- App Router: folders are routes, components run on the server by default, and any server component can fetch data.
- Nested layouts, loading and error files, streaming and Server Actions are App Router only.
- Both routers can live together, so migrate one route at a time.
Interview questions and FAQ
Is the Pages Router deprecated?
No. It is still supported and maintained. But new features are built for the App Router, and the Next.js docs recommend it for new apps.
Can I use getServerSideProps in the App Router?
No. In app/ you fetch data directly inside async Server Components. The old data functions only work in pages/.
What is the difference between layout.tsx and template.tsx?
A layout stays mounted across navigations and keeps its state. A template creates a fresh instance on every navigation, so its state and effects reset.
Are Server Components the same as server-side rendering?
No. SSR turns components into HTML on the server, and in the Pages Router every component is still hydrated with JavaScript after that. Server Components never hydrate and send no JavaScript. Client components in the App Router are still rendered to HTML on the server on the first load.
Why does my useRouter() have no query field?
Because you imported it from next/navigation in the App Router. Use useSearchParams() for the query string, and the params prop or useParams() for dynamic segments.
Comments
Post a Comment