Migrating Pages to App Router
In one line
app/ and pages/ coexist in one project with app/ taking precedence for conflicting routes, which makes an incremental route-by-route migration the only sensible plan.
What it is
Both routers can run in the same app. That is the fact the whole strategy rests on: you move one route at a time, ship each one, and never maintain a long-lived rewrite branch. If a path exists in both, app/ wins.
The translation table is most of the mechanical work. getServerSideProps becomes an await in an async server component. getStaticProps becomes a cached fetch or use cache, and getStaticPaths becomes generateStaticParams. _app.tsx and _document.tsx collapse into the root layout.tsx. next/head becomes the Metadata API. next/router becomes next/navigation, where useRouter no longer exposes query — useParams and useSearchParams split that role. API routes become route handlers, or disappear entirely when a server action replaces them.
The conceptual work is harder and less mechanical. Every component in app/ is a server component by default, so a page that used hooks, context, or event handlers throughout needs its client boundary drawn deliberately — and the temptation to put 'use client' at the top of the page and move on defeats the entire point of migrating.
A sensible order: start with a low-traffic leaf route to shake out the build and deploy path; move shared layout and providers into the root layout early, since everything depends on them; migrate data-heavy routes next, where server components actually pay; and leave the most complex interactive routes for last, when the team knows the model.
Two things to watch. Shared state between a Pages route and an App route does not exist — a client-side store is remounted when you cross between them, so plan the cut lines around navigation flows rather than by folder. And the two routers have different behaviours for scroll restoration, next/link prefetching, and 404 handling, so cross-router navigation is where the odd bugs live.
For the version upgrade itself, the codemods do real work — the Next 16 upgrade codemod handles the turbopack config move, the middleware-to-proxy rename, and unstable prefix removal, and a separate codemod migrates the async request APIs.
Why it matters
Most companies hiring for senior React roles have a Pages Router codebase and a migration in flight, so "how would you migrate this?" is a live question rather than a hypothetical. The answer they want is incremental, route by route, with a named order — not a rewrite.
Key points
app/andpages/coexist, withapp/winning conflicts — migrate one route at a time and ship each.- The mechanical mapping:
getServerSideProps→await,getStaticPaths→generateStaticParams,_app/_document→ root layout,next/head→ Metadata API. next/routerbecomesnext/navigation;querysplits intouseParamsanduseSearchParams.- Drawing the client boundary is the real work —
'use client'at the top of a migrated page wastes the migration. - Order the work: low-traffic leaf first, shared layout early, data-heavy next, most interactive last.
- Client state does not survive navigation between the two routers, so cut along navigation flows.
- Use the codemods for the mechanical parts, including the Next 16
proxyrename and async request APIs.