Lesson 65 of 81
You don't migrate a real app to the App Router in one weekend. pages/ and app/ run side by side on purpose — the only thing you actually need to know cold is which one wins when both claim the same URL.
12 min read
Every option explains itself — why the right one is right, and why each wrong one is wrong. Miss one and it comes back later on a spaced-repetition schedule.
This topic is about judgment and practical tradeoffs more than a single right answer — a mock interview is the better way to test yourself on it.
Be interviewed on this →Hints without spoilers, and a plain explanation of why a wrong answer was wrong — answered only from this lesson. Free account, no card.
Sign up freeWhat interviewers actually ask about this, and what a strong answer has to cover.
A strong answer covers
Likely follow-ups
pages/ and app/ are allowed to coexist in the same project, permanently if you want — this isn't a temporary shim, it's how Next.js expects a real migration to happen. The App Router was deliberately designed so a team can move one route at a time instead of freezing feature work for a big-bang rewrite.
The one rule that actually matters day to day: if a URL is reachable from both directories, app/ wins. pages/about.tsx and app/about/page.tsx both resolving to /about isn't an error and isn't a merge — the App Router version renders, and the Pages Router file silently never runs again. That's exactly the mechanism migration leans on: add the new app/about/page.tsx, confirm it works, then delete the now-dead pages/about.tsx — there's no window where both need to be "in sync," because only one was ever actually serving traffic.
Everything else about incremental migration follows from treating the two directories as genuinely independent route trees that happen to get merged at resolution time, not as two halves of one system that need to agree with each other. A route entirely under pages/ behaves exactly as it always did — no App Router concept leaks into it uninvited. A route you've moved to app/ gets the full App Router model: Server Components by default, the special-file conventions, async data fetching instead of getServerSideProps. There's no partial state for a single route; each one is wholly one or the other.
pages/
├── _app.tsx → still the global wrapper for every page/ route
├── dashboard.tsx → unmigrated, works exactly as before
└── api/
└── legacy-webhook.ts → unmigrated Pages Router API route
app/
├── layout.tsx → root layout — ONLY applies to routes under app/
└── settings/
└── page.tsx → migrated: this route now wins over any pages/ equivalent
app/layout.tsx wraps /settings and nothing else here — pages/dashboard.tsx still gets its shared UI from pages/_app.tsx, completely separately. If someone later adds app/dashboard/page.tsx, /dashboard switches over to it immediately and pages/dashboard.tsx becomes dead code waiting to be deleted — no flag to flip, no config to update, the precedence rule just resolves it.
app/ winning on a collision is per-route, decided independently for every URL — it's not a project-wide mode switch. A project can have 40 routes on Pages and 2 on App, fully working, indefinitely._app.tsx and _document.tsx only ever apply to pages/ routes. Moving a route to app/ doesn't inherit anything from them — the App Router's root layout.tsx is a separate, parallel root that a Pages Router route never sees either.getServerSideProps/getStaticProps have no equivalent export in app/ — moving a page means rewriting its data fetching as an async Server Component, not just relocating the file and expecting the old function to still run.pages/api/* and app/**/route.ts can run side by side for different endpoints without conflict, as two genuinely separate API surfaces — the precedence rule for colliding UI routes applies the same way to colliding API paths, but most teams migrate their API routes on their own schedule, independent of their page migrations.app/settings/page.tsx while app/settings/billing.tsx stays on Pages is fine — the router resolves each path independently, not as a subtree that must be entirely one system."We have a large Pages Router app and six months to move to the App Router without a feature freeze — what's the actual plan?" tests whether a candidate knows the two routers genuinely coexist and has an answer beyond "rewrite it gradually" — naming the collision-precedence rule, and the leaf-first ordering it implies, is the difference between a real incremental plan and a vague one.