DEV Community

ANKUSH CHOUDHARY JOHAL
ANKUSH CHOUDHARY JOHAL

Posted on • Originally published at johal.in

Retrospective: Migrating to Next.js 15 App Router Broke 30% of Our User Routes

Retrospective: Migrating to Next.js 15 App Router Broke 30% of Our User Routes

Our team decided to migrate from Next.js 14 Pages Router to15 App Router to leverage React Server Components, Server Actions, and improved performance optimizations. We followed the official incremental migration guide, tested locally, validated all routes in staging, and deployed with confidence. Within 15 minutes of production deployment, 30% of user-facing routes (including dynamic profile pages, dashboards, and settings flows) began returning 404 errors, 500 internal server errors, and broken hydration mismatches.

The Pre-Migration Plan

We adhered to Next.js recommended practices: enabled incremental adoption via the app directory alongside pages, ran the provided codemods to update deprecated APIs, and achieved 100% route coverage in our automated test suite. Staging validation passed all end-to-end tests, and we saw no errors in pre-production load testing.

The Incident: 30% Routes Down

Production deployment kicked off on a Tuesday morning. Sentry alerts fired within 10 minutes: 404 errors for /users/[id] routes, 500 errors for authenticated /dashboard paths, and blank pages for /settings flows. Analytics confirmed 30% of daily active users hit broken routes. We rolled back to Next.js 14 within 30 minutes, but support tickets spiked 400% that day, and we saw a 2% temporary churn increase.

Root Causes of the Breakage

Post-mortem analysis over 3 days revealed three core issues, totaling 30% of broken routes:

  • Dynamic Route Parameter Mismatch: App Router passes dynamic params as a prop to page components, but legacy code relied on useRouter().query from Pages Router, which is removed in App Router. This caused 18% of broken routes.
  • Server/Client Component Misclassification: Components using browser APIs (localStorage, window) were incorrectly marked as Server Components, triggering hydration errors. This accounted for 8% of broken routes.
  • Middleware Deprecation Issues: Custom auth middleware used req.query, which is unavailable in App Router middleware. This broke all protected routes, accounting for 4% of breakages.

Debugging the Issues

We used Next.js 15's built-in next build --debug to audit route compilation, Sentry's App Router SDK for error tracing, and Chrome DevTools to identify hydration mismatches. The biggest challenge was reproducing issues: local development and staging builds behaved correctly, but production minification and edge caching exposed the bugs.

Fixes and Remediation

We implemented targeted fixes over 2 weeks:

  • Ran a custom codemod to replace all useRouter().query references with the params prop, covering 120+ dynamic route files.
  • Audited all components, added "use client" directives to any component using browser APIs, and resolved all hydration errors.
  • Rewrote middleware to use App Router's NextRequest object, removing all deprecated req.query access.
  • Configured explicit cache revalidation for user data fetches, using revalidatePath for dynamic routes and revalidateTag for shared data.

We re-deployed with a 1% canary rollout, then 10%, then full traffic. No route breakages occurred, and all metrics improved as expected.

Lessons Learned

  • Never skip canary deployments: staging does not replicate production minification, caching, or traffic patterns. Always roll out to 1% of traffic first.
  • Audit all deprecated APIs upfront: Use Next.js codemods before starting migration, and search for removed APIs (like useRouter().query) across the entire codebase.
  • Test dynamic routes explicitly: Our test suite covered static routes but missed edge cases for dynamic [id] routes with special characters or missing params.
  • Monitor post-deployment aggressively: Set alerts for 4xx/5xx error rate spikes, and have a rollback plan tested and ready.

Conclusion

The migration to Next.js 15 App Router delivered on its promises: 40% faster Time to Interactive, simplified form handling via Server Actions, and better SEO performance. The 30% route breakage was a painful but valuable lesson. For teams planning the same migration: take it slow, test every edge case, and prioritize canary deployments over full rollouts.

Top comments (0)