{"slug": "i-built-an-ai-agent-that-migrates-next-js-pages-router-to-app-router", "title": "I built an AI agent that migrates Next.js Pages Router to App Router", "summary": "A developer built migrate-bot, an AI agent that automates the end-to-end migration of Next.js Pages Router projects to App Router. The tool handles semantic changes including converting `getStaticProps` to async Server Components, `next/router` to `next/navigation`, and restructuring `pages/` to `app/` with dynamic routes. The serverless pipeline runs through stages from queuing to PR readiness, with automatic refunds triggered if a migration fails mid-process.", "body_md": "Most Next.js teams have a Pages Router → App Router migration sitting in their backlog. It's mechanical but careful work, and it keeps getting deprioritized. I built [migrate-bot](https://migrate-bot.dev) to automate it end-to-end, and this post is about how it works under the hood.\n\nApp Router isn't just \"move files to a new folder\". The semantic changes:\n\n`getStaticProps`\n\n/ `getServerSideProps`\n\n→ `async`\n\nServer Components`getStaticPaths`\n\n→ `generateStaticParams`\n\n`next/router`\n\n→ `next/navigation`\n\n(`useRouter`\n\n/ `usePathname`\n\n/ `useSearchParams`\n\n)`next/head`\n\n→ the Metadata API (`export const metadata`\n\n)`pages/_app.tsx`\n\n+ `pages/_document.tsx`\n\n→ `app/layout.tsx`\n\n`pages/api/x.ts`\n\n(single handler) → `app/api/x/route.ts`\n\n(`export async function GET/POST/...`\n\n)`pages/`\n\n→ `app/`\n\nrestructure, including `[slug]`\n\ndynamic routesThe whole thing runs serverless + ephemeral:\n\n```\nqueued → analyzing → planning → migrating → verifying → pr_ready\n                                                ↓ (on failure)\n                          aborted_blocker → refunding → refunded\n```\n\nEach transition is persisted to D1 so every job is observable, and a mid-pipeline crash transitions to `aborted_blocker`\n\n→ automatic refund rather than leaving the customer charged for nothing.\n\nEarly on, every repo with a `pages/_document.tsx`\n\nfailed. The planner maps both `_app.tsx`\n\nand `_document.tsx`\n\nto the same target (`app/layout.tsx`\n\n), and my migrate step recorded the second one as a \"failed task\" when it detected the target was already written. The pipeline treated any failed task as fatal — so the whole migration aborted.\n\nThe fix was to distinguish *intentional skips* (planned target collisions) from *real failures* (the LLM aborting or erroring). I added a `skippedTaskIds`\n\nfield separate from `failedTaskIds`\n\n, and surfaced the skip in the PR description so the customer knows to manually merge any custom `<Html>`\n\nattributes from their `_document.tsx`\n\n.\n\nSince this rewrites real production code, the trust mechanisms matter:\n\n`next build`\n\n) can't pass for reasons attributable to the service.It's live at [migrate-bot.dev](https://migrate-bot.dev). Pricing is per-repo by file count ($99 / $249 / $499). I'd genuinely value feedback on the approach — especially the pricing shape and the draft-PR + refund trust model.\n\n(Currently not offered to EEA/UK/Switzerland residents due to GDPR territorial scope; revisiting post-launch.)", "url": "https://wpnews.pro/news/i-built-an-ai-agent-that-migrates-next-js-pages-router-to-app-router", "canonical_source": "https://dev.to/garoro0920/i-built-an-ai-agent-that-migrates-nextjs-pages-router-to-app-router-5747", "published_at": "2026-05-25 14:36:24+00:00", "updated_at": "2026-05-25 15:06:16.979813+00:00", "lang": "en", "topics": ["ai-agents", "ai-tools", "ai-products", "ai-startups", "ai-infrastructure"], "entities": ["Next.js", "Pages Router", "App Router", "migrate-bot", "D1"], "alternates": {"html": "https://wpnews.pro/news/i-built-an-ai-agent-that-migrates-next-js-pages-router-to-app-router", "markdown": "https://wpnews.pro/news/i-built-an-ai-agent-that-migrates-next-js-pages-router-to-app-router.md", "text": "https://wpnews.pro/news/i-built-an-ai-agent-that-migrates-next-js-pages-router-to-app-router.txt", "jsonld": "https://wpnews.pro/news/i-built-an-ai-agent-that-migrates-next-js-pages-router-to-app-router.jsonld"}}