Blog
4 min read

Next.js App Router vs Pages Router: Differences and Whether to Migrate

Next.js has two routers: the older pages/ directory and the newer app/ directory built on React Server Components. How routing, data fetching, layouts and API routes differ, which one new projects should use, and a sensible incremental migration path.

Next.js ships two routing systems in the same framework:

  • Pages Router — the original, using a pages/ directory. Stable, well understood, and still supported.
  • App Router — introduced in Next.js 13, using an app/ directory and built on React Server Components. The recommended default for new projects.

They can coexist in one app, which is what makes gradual migration possible.

Side by side

Pages Router (pages/) App Router (app/)
Route file pages/blog/[slug].tsx app/blog/[slug]/page.tsx
Default component type Client (hydrated) Server Component
Data fetching getServerSideProps, getStaticProps async components, fetch, direct DB calls
Layouts _app.tsx + manual wrappers Nested layout.tsx per folder
Loading / error UI Manual loading.tsx, error.tsx, not-found.tsx
Mutations API routes + client fetch Server Actions (plus route handlers)
API endpoints pages/api/*.ts app/api/*/route.ts (route handlers)
Metadata / SEO next/head metadata export / generateMetadata
Streaming Limited Built in with Suspense
Caching model Simpler More layers to understand

Routing

Pages Router: the file is the route.

pages/index.tsx           → /
pages/blog/[slug].tsx     → /blog/:slug
pages/api/users.ts        → /api/users

App Router: folders are routes; special files inside define what renders.

app/page.tsx                    → /
app/blog/[slug]/page.tsx        → /blog/:slug
app/blog/layout.tsx             → wraps every /blog page
app/blog/[slug]/loading.tsx     → shown while the page loads
app/api/users/route.ts          → /api/users

Folders in (parentheses) group routes without affecting the URL, and files like components.tsx placed in a route folder aren't routes unless named page or route.

Data fetching

Pages Router:

export async function getServerSideProps({ params }) {
  const post = await db.post.find(params.slug)
  return { props: { post } }
}
export default function Post({ post }) { … }

App Router:

export default async function Post({ params }) {
  const { slug } = await params
  const post = await db.post.find(slug)
  return <article>…</article>
}

The App Router version is shorter, and the data fetching sits next to where it's used. Each component can fetch what it needs.

Why the App Router is the default now

  • Less JavaScript to the browser, since most components stay on the server.
  • Nested layouts that persist between navigations (no re-rendering the sidebar).
  • Streaming — show parts of the page as their data arrives.
  • Server Actions for forms without hand-written API routes.
  • New Next.js features land here first.

Why some stay on Pages

  • It works. A stable Pages app has no urgent reason to move.
  • Mental model. Server vs client components, caching and revalidation have a learning curve, and mistakes cause confusing bugs.
  • Library compatibility. Some older libraries assume everything is client-side and need "use client" wrappers.

Choosing for a new project

Use the App Router. It's what the docs, examples and AI tools default to now. If an AI tool generates getServerSideProps in a new App Router project, it's mixing patterns — tell it which router you're using in your CLAUDE.md.

Migrating incrementally

You don't need a big-bang rewrite. Both directories work together:

  1. Upgrade Next.js on the Pages Router first and fix anything that breaks.
  2. Create app/layout.tsx (the root layout replacing _app and _document for app routes).
  3. Move one route at a time, starting with simple, mostly static pages. A route must exist in only one directory.
  4. Convert getServerSideProps/getStaticProps into async Server Components; move interactive parts into small "use client" components.
  5. Replace next/head with the metadata export.
  6. Move API routes to route handlers when convenient — pages/api keeps working meanwhile.
  7. Test each moved route (navigation between old and new routes does a full page load until both sides are on the same router).

Coding agents are good at this kind of mechanical, route-by-route migration — especially with a test suite and a running app to check against. (Using Claude Code on a large codebase)

Self-hosting either

Both routers work with next build && next start on any Node server. (Self-hosting Next.js)

The summary

  • Pages Router: pages/, getServerSideProps, client components by default.
  • App Router: app/, Server Components, nested layouts, Server Actions, streaming.
  • New projects → App Router. Stable Pages apps → migrate when there's a reason.
  • Migrate route by route; both routers can run side by side.

EasySpawn runs Next.js on a persistent server with Postgres alongside, so Claude Code can migrate routes, run the app and check each page as it goes. See how it works or join the waitlist.

Related: React Server Components Explained · What Is Next.js? · Astro vs Next.js · Self-Hosting Next.js Without Vercel

Keep reading