Migrating a Pages app to App Router without drama

Table of Contents
Table of Contents
I migrate a Next.js Pages app to App Router one URL at a time. I copy the data rules, keep a rollback, and I do not rewrite the whole tree in a weekend.
That is the migration. Not a big-bang folder rename.
I’m Aris Setiawan. I ship Next.js for client work. I already wrote the App Router I keep in `app/`, when I still touch Pages, and the API routes I trust. This post is the move itself: how a Pages app becomes App Router without drama.
The offer page is Hire a Next.js developer. This is the practice, not a rewrite sales deck.
Start with one URL
I pick one route. Not the home page first unless it is the simplest. Not “the dashboard” as a tree of twenty files. One URL I can name, ship, and roll back.

I write down three things before I touch files: where the session is read, what the page fetches, and what happens after a write. If I cannot say those three, I am not migrating that URL yet.
I leave every other Pages route alone. Mixed trees are fine when each URL has one owner. Drama starts when /account is still Pages and /account/settings is App Router with a different session story.
Map the old calls, do not rename files
getServerSideProps does not become a Server Component by renaming the file. Cookies, cache, and the client boundary have to be decided again.

getServerSideProps. The request is the cache. In App Router I use a Server Component that reads the cookie and fetches with no-store or force-dynamic. If I leave a cookie fetch cacheable, two users will share a page. I already wrote that bug once. I will not ship it again.
getStaticProps plus revalidate. This one maps cleanly. A Server Component with a revalidate window or a tag is the replacement. I still name what busts it after a write. A static page with no tag is how a CMS edit sits invisible until someone redeploys.
getStaticPaths. generateStaticParams. Same job, different file. I generate the params I care about and I decide what happens for a path I did not list.
pages/api. A route.ts for external callers, or a Server Action when the write starts in our UI. I do not keep a new pages/api folder next to a working app/ tree for the same product surface.
_app and _document. app/layout.tsx and metadata. I do not paste _app into a client root layout so the old providers “just work.” Providers that need the browser stay in a small client shell. The layout stays a Server Component when it can.
Keep a rollback before you cut over
I do not delete the Pages file on the first deploy of the App Router version. I prove the new URL. Then I remove the old one.

Before cutover I check four things:
Session is the same on both trees. Same cookie name, same auth helper, same “who is this” answer.
Cache tags are named. If the write path cannot say which tag to revalidate, the list page will stay stale.
The client boundary is small. A 'use client' on the whole page so hooks keep working is how you lose the reason you moved.
Rollback is one deploy. If the new route is wrong, I can put the Pages file back without a git archaeology session.
If any of those four is missing, I am not cutting over. I am still migrating.
What I migrate first
Marketing pages with clear data. A blog index. A public product page that already had getStaticProps. Those teach the team the new file shapes without putting checkout at risk.
Then account pages that read a session. Those force the auth and cache sentence into the open.
Checkout, webhooks, and anything with money or signatures wait until the boring routes are boring. I already covered which API handlers I trust. The migration does not invent a new rule there.
What I skip
A weekend “finish the migration” plan with no owner per URL.
Deleting pages/ while half the dashboard still imports from it.
Flipping the root layout to 'use client' so Pages habits fit.
Treating a renamed folder as a finished Next.js migration. The data rules are the migration.
Starting a brand new product in Pages so the old contractors feel at home. New work starts in app/.
What I will take
I take the messy case: a Pages app with _app, a pile of getServerSideProps, a few pages/api webhooks, and a product that cannot go dark for a week. I move one URL, prove it, then the next. I will also take a clean app/ tree on a new product. I will not take a rewrite that ships half a checkout and hopes for the best.
If you want this owned on your product, that is the work on Hire a Next.js developer. Send the repo or the ticket.
Next in this series is what a Next.js developer should own on your product.



