Verified against Claude Code · 2026-07-19
Turn a Pages Router codebase into a phased App Router migration, not a risky rewrite
Produces a route-by-route migration plan that runs Pages Router and App Router side by side, converting data-fetching methods and shared layout as it goes.
The prompt
Ready to copy — highlighted parts are example details you can swap.
<context> Current pages/ directory structure: pages/index.tsx, pages/blog/[slug].tsx (getStaticProps), pages/dashboard.tsx (getServerSideProps, behind auth), pages/api/checkout.ts Data-fetching methods currently used and where: getStaticProps on the blog, getServerSideProps on the dashboard, getInitialProps on the legacy account page Shared layout/elements currently in _app.tsx / _document.tsx: A ThemeProvider, a global Header/Footer, and a custom <Html lang> in _document.tsx </context> <task> Produce a phased migration plan from the Pages Router to the App Router for this codebase. Pages Router (pages/) and App Router (app/) can coexist in the same Next.js project, so plan this as an incremental, route-by-route migration — not a big-bang rewrite done on one branch all at once. </task> <migration_plan_requirements> - Order routes by risk and traffic: recommend migrating low-traffic, low-risk routes first to validate the approach, and the highest-traffic/most-critical routes last, once the pattern is proven. - For each data-fetching method in use, state its App Router equivalent and the mental model shift, not just a mechanical swap: - getStaticProps becomes an async Server Component that fetches directly, with the equivalent caching behavior expressed via fetch's cache/revalidate options, not a special function. - getServerSideProps becomes an async Server Component with a fetch call that opts out of caching (cache: 'no-store'), or reads a dynamic API like cookies()/headers() that forces dynamic rendering. - getInitialProps: flag this one explicitly as needing the most rework, since it ran on both server and client and has no direct App Router equivalent. - Move shared elements from _app.tsx/_document.tsx into app/layout.tsx (the root layout) once, not per-route — this should happen early, since both routers can share global styles/providers through careful setup, but duplicate providers during the transition is a common source of bugs. - Convert pages/api routes to app/api/.../route.ts one at a time, in whatever order matches the page migration, not as a separate all-at-once phase. - Call out any Pages Router-only APIs still in use (next/router's useRouter differs from next/navigation's, custom _error.tsx handling, etc.) that need a direct behavioral adjustment, not just a file move. </migration_plan_requirements> <output_format> A phased table: Phase | Routes/files migrated | Data-fetching conversion needed | Risk notes. Then a short "do this first, regardless of phase" list for the _app.tsx/_document.tsx to root layout move. </output_format>
Customize the highlighted detailsoptional — the prompt above already works
Why this works
The single fact that makes this migration tractable instead of terrifying is one most teams don't realize going in: pages/ and app/ coexist in the same Next.js project, and Next.js resolves routes across both, so migration can happen one route at a time on main instead of on a long-lived branch that has to land all at once. Mapping each data-fetching method to its App Router equivalent as a mental-model shift rather than a mechanical rename matters because getServerSideProps and a no-store fetch in a Server Component aren't quite the same thing — the latter is just one fetch call opting out of caching, while the whole component around it defaults to static unless something forces it dynamic, which is a different way of thinking about the page. Explicitly flagging getInitialProps as the hard case, and ordering the plan by risk rather than alphabetically, both come from the same practical bias: prove the pattern on something that doesn't matter much before touching the route that generates revenue.
What you get back
Phase 1: migrate pages/blog/[slug].tsx (getStaticProps to an async Server Component with a cached fetch) — low traffic, low risk, validates the pattern. Phase 2: move _app.tsx's ThemeProvider and Header/Footer into app/layout.tsx once, shared by both routers going forward. Phase 3: pages/dashboard.tsx (getServerSideProps to a Server Component using cookies() to force dynamic rendering, since it's behind auth). Phase 4 (last, highest risk): the legacy account page using getInitialProps — flagged for a rewrite, not a mechanical conversion, since it has no direct equivalent.
Verified against
Claude Code Sonnet 4.6 · 2026-07-19
Cursor 2.1 · 2026-07-23
Changelog
- 2026-07-19 — Initial version, tested against Claude Code on a mixed Pages/App Router Next.js 16 project.
Need this built into your business?
If a prompt isn't enough — custom software, built and maintained for you — that's Scult's day job.
EXPLORE CUSTOM SOFTWARE

