Skip to content
Next.js8 min read

Migrating from Pages Router to App Router Safely

You do not need a big-bang rewrite to adopt the App Router. A staged migration plan, the data-fetching translations, and the traps that catch teams mid-move.

The Pages Router still works and is still maintained, so an App Router migration is never an emergency — but new capabilities land in the App Router, and codebases straddling old patterns age badly. The good news is that Next.js supports both routers in the same application simultaneously, which turns a terrifying rewrite into a sequence of small, shippable moves.

Both routers can coexist

An app directory and a pages directory can live side by side; routes must not conflict, but each route can be served by either router. This is the entire migration strategy in one sentence: move routes across one at a time, deploy continuously, and keep the site working throughout. There is no flag day.

Budget for the costs of running both, too: shared components must work under each router's constraints, navigation between the two routers triggers full page loads rather than client transitions, and your build carries some duplication until the move completes. All manageable — but plan for months of steady progress, not a heroic fortnight.

A migration order that works

  • Upgrade Next.js and React on the Pages Router first, so the migration is not tangled up with version bumps
  • Build the root layout in app, replicating _app and _document
  • Move shared components and make them boundary-clean — most should not need 'use client'
  • Migrate low-risk static pages first to prove the pipeline, then the high-value dynamic routes
  • Finish with the awkward ones: auth flows, heavily dynamic pages, anything using router internals

Resist the temptation to redesign pages while migrating them. One kind of change per pull request — a migrated page should be pixel-identical, or reviewers cannot tell refactor from regression.

Invest an early week in team fluency as well. The App Router changes the mental model — Server Components, new caching semantics, new conventions — and a team migrating code it does not yet understand produces working pages with Pages Router thinking inside them, which is the worst of both worlds.

Translating data fetching

The mechanical part is rewriting page-level data functions. getServerSideProps becomes an async Server Component; getStaticProps becomes the same plus a revalidate export; getStaticPaths becomes generateStaticParams.

tsx
// before: pages/products/[id].tsx
// export async function getServerSideProps(ctx) { ... }

// after: app/products/[id]/page.tsx
export default async function ProductPage({ params }: Props) {
  const { id } = await params;
  const product = await getProduct(id);
  if (!product) notFound();
  return <ProductView product={product} />;
}

The traps

  • Router imports: next/router becomes next/navigation, and the API differs — useRouter no longer exposes query; use useParams and useSearchParams
  • useSearchParams requires a Suspense boundary above it, or the whole page opts into client-side rendering
  • Data fetched in _app has no direct equivalent — layouts fetch their own data, and layouts do not receive route params
  • Caching defaults differ between routers, so verify the rendering mode of each migrated page rather than assuming
  • Global CSS and context providers need a home in the root layout, usually via a small client providers component

Testing and rollout

Lean on end-to-end tests over unit tests here — they survive the refactor because they test URLs and behaviour, not implementation. Snapshot the rendered HTML of key pages before and after, check metadata and structured data survived, and watch Core Web Vitals per route as pages move across. Migrated pages should get faster; if one gets slower, something about its caching or component boundaries is wrong, and it is easier to fix while the diff is fresh. A simple checklist per page — data, metadata, vitals, behaviour — turns each migration pull request into a routine rather than an event.

Partway through a migration that has stalled? That is a familiar rescue job for STRCLI — we are happy to take a look.

Start your project

Have an idea? Let's ship it together.

Tell us what you're building — we'll reply within one business day with an honest take and a clear next step.