Next.js App Router vs Pages Router: When to Migrate and What Changes
Compare Next.js routing models, data-fetching patterns, layouts, and the work involved in an incremental migration.
A Next.js application can use the Pages Router, the App Router, or both during a migration. The choice affects data fetching, rendering, layouts, and the work needed to maintain existing routes.
Compare the required capabilities with the migration effort before scheduling the change.
The Fundamental Architectural Difference
The Pages Router is a file-based routing system built on top of traditional React. Every file in pages/ exports a React component that Next.js renders. Data fetching happens through special exported functions: getServerSideProps for server-side data, getStaticProps for static data, and getStaticPaths for dynamic static routes.
The App Router is a different runtime. It is built on React Server Components, which means components run on the server by default and never ship JavaScript to the client unless they explicitly need to. The file conventions are different, the data fetching model is different, and the caching behavior is different.
These are not shallow differences that a script can translate. They reflect genuinely different approaches to how a Next.js application renders.
What Changed Between the Two
Data Fetching
Pages Router: Data fetching happens through exported functions at the page level. getServerSideProps runs on every request. getStaticProps runs at build time. Components themselves cannot fetch data — they receive it as props.
App Router: Any server component can await data directly in its body. There are no special exported functions. Data fetching is distributed across the component tree rather than centralized at the page level. This removes prop-drilling but requires a new mental model.
Caching
Pages Router: Caching is configured at the page level through getStaticProps and ISR via the revalidate export. No component-level caching.
App Router: Caching is granular. Individual fetch calls, database queries wrapped in unstable_cache, and entire components can be cached with separate revalidation windows. The system is more powerful but also more complex to reason about.
Layouts
Pages Router: Layouts are implemented by wrapping components in _app.tsx or by composing layout components manually in each page. Nested layouts require manual composition.
App Router: layout.tsx files are a first-class primitive. Layouts nest automatically based on the folder hierarchy. Each layout segment stays mounted during navigation within its scope — the sidebar does not remount when navigating between dashboard pages.
API Routes
Pages Router: API routes live in pages/api/. They export handler functions and work like Express handlers.
App Router: API routes are Route Handlers (route.ts files) that export named HTTP method functions (GET, POST, etc.). Server Actions handle most mutation use cases that previously required API routes.
What the Migration Involves
Both routers can coexist in the same Next.js project. You can run the App Router and Pages Router simultaneously during a migration — pages in the pages/ directory continue to work while you move routes to app/ incrementally.
The practical work of migration includes:
Rewriting data fetching. Every page that uses getServerSideProps or getStaticProps needs to be rewritten as an async server component. The logic is often similar, but the structure is entirely different.
Converting layouts. Custom layout wrappers in _app.tsx become layout.tsx files. This often requires splitting shared layout code into the right level of the hierarchy.
Auditing client vs server boundaries. Interactive components need use client. State management, context providers, and anything using hooks needs to be identified and handled explicitly.
Replacing useRouter usage. The useRouter from next/router (Pages Router) is different from the one in next/navigation (App Router). Programmatic navigation code needs updating.
Testing caching behavior. The App Router's caching model is more aggressive than the Pages Router's. Pages that should be dynamic need explicit configuration to avoid serving stale content.
When to Migrate
Migrate now if:
- You are starting a new project (use the App Router from the beginning)
- Your project is small enough that a full rewrite is feasible in a sprint or two
- You want streaming, granular caching, or Server Actions and are willing to invest in the migration
Wait or migrate incrementally if:
- Your application is large and the Pages Router serves it well
- Your team is not yet fluent in Server Components and the migration would introduce risk
- You have extensive test coverage tied to
getServerSidePropspatterns that would need to be rewritten
Do not migrate if:
- The Pages Router is working correctly and you have no specific reason to change
- Your team is shipping features and a migration would cause prolonged disruption without a clear return
For an existing application, weigh the features you need against migration size, team capacity, and the cost of maintaining both routing systems during the transition.
What Stays the Same
Not everything changed. Several things carry over:
next.config.jsconfiguration (with some additions)- The
ImageandLinkcomponents work the same way - Environment variable conventions are unchanged
- Deployment and hosting work identically
- TypeScript support and the overall DX remain strong
The migration is architectural, not infrastructural. Your deployment pipeline, CI configuration, and hosting setup do not need to change.
Document which routes will move, how behavior will be tested, and what would justify leaving a route on the Pages Router for now.