Next.js App Router vs Pages Router: Key Differences, Data Fetching and Migration Guide

Next.js App Router vs Pages Router - CodingHumans

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

TopicPages Router (pages/)App Router (app/)
Default component typeClient component (all code ships to the browser)Server Component (no JS shipped unless you add 'use client')
Route definitionEach file is a routeEach folder is a route segment, page.tsx makes it public
LayoutsOne global _app, per-page layout patterns by handNested layout.tsx files that persist between navigations
Data fetchinggetServerSideProps, getStaticProps, getStaticPathsasync components that await data directly, plus generateStaticParams
Loading and errorsManual state in each pageloading.tsx (Suspense) and error.tsx (error boundary) per segment
StreamingNot supportedBuilt in through Suspense
MutationsAPI routes called from the clientServer Actions, or Route Handlers
SEO tagsnext/headmetadata export or generateMetadata
Router hooksnext/routernext/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.

Folder structure comparison between Next.js Pages Router and App Router
The same routes in both routers.

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 call notFound().
  • route.ts: an HTTP endpoint (replaces pages/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 a metadata object. If the title depends on data, export an async generateMetadata function. Next.js merges metadata from nested layouts and removes duplicates.
  • API endpoints: pages/api/hello.ts exports one handler. app/api/hello/route.ts exports one function for each HTTP method (GET, POST and so on), using the standard Request and Response objects.
  • 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, usePathname and useSearchParams from next/navigation, not next/router. The new useRouter has no query or pathname fields.

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.

  1. Upgrade Next.js and React first, and fix any warnings while you are still on the Pages Router.
  2. Create app/layout.tsx with the <html> and <body> tags. Move global CSS and providers from _app and _document into it. Providers that use context go in a small client component.
  3. Move a simple, low-risk page first, like an About page. Create app/about/page.tsx and delete pages/about.tsx.
  4. Replace data functions: getServerSideProps or getStaticProps becomes an await inside the async page, and getStaticPaths becomes generateStaticParams.
  5. Mark interactive components with 'use client'. Any component that uses useState, useEffect or onClick needs it.
  6. Swap imports: next/router to next/navigation, next/head to the metadata API.
  7. Move API routes to route.ts handlers when it suits you. pages/api keeps working until then.
  8. 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.

Key takeaways
  • 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

Answered 0/0Correct 0