Next.js App Router vs Pages Router: Lessons From 12 Project Migrations
Next.jsFrontendReact

Next.js App Router vs Pages Router: Lessons From 12 Project Migrations

What changes, what breaks, what to migrate first, and what to leave alone. Hard-earned migration lessons from moving 12 client projects from Pages Router to App Router.

HJ
Hassan Javed
March 2026
11 min read

When to migrate (and when not to)

I've migrated 12 production Next.js apps from Pages Router to App Router in the last 18 months. Some migrations took 3 days. One took 6 weeks. The difference wasn't app size — it was complexity in three specific areas: authentication, data-fetching patterns, and dynamic imports.

Migrate when:

You're building new pages anyway and want SSR + streaming for them
Your team is sick of getServerSideProps boilerplate
You need React Server Components (Pages Router doesn't have them)
You want Server Actions for form submissions

Don't migrate when:

The app is in maintenance mode (no new features planned)
Your auth is deeply integrated with NextAuth on the Pages Router (NextAuth's App Router support is now solid in v5, but earlier versions cause real pain)
You have a lot of getServerSideProps with custom logic that doesn't translate cleanly

Migration playbook

Step 1: Run them in parallel

You don't have to migrate all-at-once. App Router and Pages Router coexist in the same project. Move new routes to app/, keep old routes in pages/, ship continuously.

This single decision turns a "scary 6-week migration" into "incrementally migrate over 3 months."

Step 2: Move the layout first

pages/_app.js and pages/_document.js map to app/layout.js. This is your first migration win — your global providers, fonts, and metadata move once.

Step 3: Migrate the simplest leaf pages first

Pick a static or near-static page. Maybe a privacy policy, or an about page. Verify in the browser. Ship.

Step 4: Migrate data-fetching pages

This is where it gets real. Old: getServerSideProps + props. New: async function in the page component itself.

Catch: fetch in App Router is cached aggressively by default. If you were using getServerSideProps to bust the cache on every request, you need { cache: "no-store" } or export const dynamic = "force-dynamic".

Step 5: Migrate API routes (mostly)

pages/api/users.jsapp/api/users/route.js. The Web standards (Request, Response) replace Express-style req/res. Most things translate; some middleware patterns need rewriting.

Step 6: Authentication is its own project

If you're on NextAuth, upgrade to v5 (Auth.js). v5's App Router support is solid; v4's was painful. If using Clerk, their App Router SDK is the cleanest of any auth library I've used.

Step 7: Dynamic imports

dynamic() only works inside client components in App Router. If you need to lazy-load a component from a server component, wrap it in a client component first.

What breaks

Things that broke for me on at least one of the 12 migrations:

1.Custom _app.js logic that ran on every page (analytics, error boundaries) — needs to move into a client component wrapper inside layout.js
2.getInitialProps — fully gone in App Router, must be rewritten
3.next/router — replaced by next/navigation (useRouter, usePathname, useSearchParams)
4.Image optimization — same next/image API, but fill mode requires a relative-positioned parent
5.CSS-in-JS — emotion, styled-components: each has Server Components support now but you have to wrap in a client provider
6.Custom 404 / 500 — pages/404.js becomes app/not-found.js; pages/500.js becomes app/error.js (signature differs)

What surprised me

Server Actions are great. Form submissions without API endpoints. Worth the migration alone for forms-heavy apps.
Streaming with Suspense is rarely worth it for small apps. You think you want streaming. You usually don't. Just render the page server-side and let it land.
The cache behavior is opinionated. Read the docs on fetch caching, revalidate, and dynamic configs once carefully. Skip this and you'll have a stale-data bug in production.

Should you migrate today?

If you're on Next.js 14.x and Pages Router:

New project: App Router. Don't even consider Pages.
Existing project, actively developed: start moving new routes to App Router incrementally. Aim for full migration in 3-6 months.
Existing project, maintenance only: don't migrate. Pages Router is supported.

When clients hire me for this

A typical Pages → App Router migration engagement is 2-4 weeks for a medium SaaS app. Deliverable is a fully-migrated app with the test suite passing, deployment pipeline updated, and a runbook for the team.

If you have a Next.js project on Pages Router and want a senior to lead the migration, contact me. React + Next.js work is part of my MERN engagement track alongside Web3.

Related Reads

You might also like