Next.js App Router vs Pages Router: Whether—and How—to Migrate
Decide whether to migrate a Pages Router app, then see what changes in route files, layouts, rendering, data fetching, and navigation.

An existing Pages Router app does not need a wholesale rewrite. If its routes are stable and its page-level data fetching works for the team, keep them. Consider the App Router for a new section—or a carefully chosen existing route—when nested layouts, Server Components, or component-level data fetching would simplify the work. Both routers can serve routes in one project, so this can be a route-by-route decision rather than a launch-day conversion.
The App Router was introduced in Next.js 13. The Next.js migration guide describes the transition from Next.js 12 to 13; use migration guidance matching your installed version for implementation details. The architectural differences below are the useful starting point for deciding what to move.
What changes when a route moves
Both routers use files to define routes, but they assign different jobs to those files. In the Pages Router, pages/reports.tsx defines /reports. In the App Router, a reports route segment contains app/reports/page.tsx. The App Router also gives route segments special files for shared UI: app/reports/layout.tsx, for example, can wrap pages deeper in that section.
That changes how you organize a section, not merely how you spell its paths. A Pages Router application commonly puts an application-wide wrapper in pages/_app.tsx and builds any more specific layout patterns itself. App Router layouts can be nested by route segment. If several related screens need the same section navigation, that is a concrete reason to consider moving the section together. See the routing and layout comparison for the distinction between _app and segment layouts.
Check the server/client boundary before moving UI
The rendering assumption also changes: pages in app/ are Server Components by default, whereas the migration guide describes pages in pages/ as Client Components. A route that currently mixes data access with interactive or browser-dependent UI deserves a closer look. Plan which parts belong in a client-side boundary instead of assuming the whole page can move unchanged into an App Router server page. Check third-party UI libraries as part of that review; not every library works comfortably with Server Components.
This is a reason to choose a small, understandable first route. It is not, by itself, a reason to replace a stable Pages Router section.
Move the data requirement, not the data-fetching function
Pages Router data requirements commonly live in page-level functions: getStaticProps for build-time data, getServerSideProps for request-time data, and, in older code, getInitialProps. Those functions are not the App Router recipe. In the App Router model, a Server Component can await data directly; components can own separate data requirements, which also makes parallel loading strategies possible.
Here is the same /reports page expressed both ways. These are illustrative fragments in the Next.js 13 migration context, not a complete application. Both assume an existing server-side getReport() loader in lib/reports that returns an object with a title. The example deliberately makes no claim about how that loader caches or refreshes data.
Pages Router — pages/reports.tsx:
import { getReport } from '../lib/reports';
export async function getServerSideProps() {
const report = await getReport();
return { props: { report } };
}
export default function ReportsPage({ report }: { report: { title: string } }) {
return <h1>{report.title}</h1>;
}
App Router — app/reports/page.tsx:
import { getReport } from '../../lib/reports';
export default async function ReportsPage() {
const report = await getReport();
return <h1>{report.title}</h1>;
}
The important change is where the requirement lives: the first page receives props from a Pages Router function; the second awaits the loader in a Server Component. Do not infer identical execution or caching behavior from their similar output. Work out those requirements for the installed Next.js version when replacing a real route. If the page also has interactive controls, review that client-side UI separately from this server-side data example.
A compact migration map helps prevent accidental API mixing:
| Existing Pages Router code | App Router direction |
|---|---|
getStaticProps or getServerSideProps |
Fetch data in Server Components; review the intended build-time or request-time behavior rather than copying the function into app/. |
getInitialProps |
Reconsider its data requirements in the Server Component model. |
pages/api API Routes |
Leave them in place if they still serve the app. App Router Route Handlers are an alternative for request handlers, not a prerequisite for moving pages. |
The official migration guide explicitly says that pages/api routes continue working during migration and describes Route Handlers as the App Router alternative using Web Request and Response APIs.
Pick a boundary you can test
Choose one route or a related section with a clear benefit. Identify its current page file, _app dependencies, data functions, interactive UI, and libraries. Add the corresponding app/ route using page.tsx; recreate section-wide UI with a nested layout where it helps. Move data requirements into Server Components, and keep browser-dependent interaction on the client side. Let other, unmigrated routes remain in pages/, along with any existing pages/api routes.
Choose the boundary with navigation in mind. In the documented migration setup, moving between routes served by different routers causes a hard navigation, and next/link does not automatically prefetch across them. Test the links users actually follow across that boundary; a frequently traversed section may be a better migration unit than scattered individual pages.
The decision is therefore fairly practical: keep Pages Router routes that already meet the app’s needs; try App Router for new work when its structure helps; and migrate existing routes selectively when their layouts, server/client split, or data requirements justify the change. An unchanged route does not need to move just because its neighbor did.




