NextJS App Router Migration

A deep dive into the migration process, including the challenges faced and the solutions implemented.

NextJS

Why we decided to migrate

Our frontend application at Skipify was built on Next.js Page Router, which had served us well but was starting to show its age. With the release of the App Router, we saw an opportunity to modernize: React Server Components, nested layouts, streaming, and a more flexible architecture. The challenge? Migrating real production flows without slowing down our users.

The App Router wasn't just a drop-in replacement. We needed to move existing flows safely, avoid disruptions for shoppers, and prove that the new architecture could match (or beat) the performance we already had.

Starting small

To test the waters, we started by using App Router on an entirely new flow we were building out. The flow was shipped smoothly, the developer experience was great, and it gave us confidence to start migrating our existing flows.

To minimize risk for existing flows, we used a phased approach:
  • Tackle one user flow at a time
  • Update and use "use client" to ensure backwards compatibility
  • Keep both App and Page Router routes in place
  • Use a Launch Darkly flag to switch between the two instantly

Having this safety net meant we could push App Router into production without fear, with the ability to roll back instantly if needed. The first user flow we migrated went smoothly. We were able to ship it to production without any issues and I removed all the old Page Router code to make it official. But during the second migration, Datadog flagged a latency regression in production. I quickly rolled back with the feature flag and avoided user impact.

Digging deeper

That's when I dug deeper. I split lower environment traffic 50/50 between Page Router and App Router to gather clean performance data. Using Datadog and Chrome DevTools, I measured end-user latency, page load performance, and how assets were being served. The data confirmed App Router was, on average, about 0.5s slower when looking at the latency metrics we were tracking.

I presented my data to the team along with the recommendation that we take the slight hit on latency and work to improve the performance of the App Router.

Turning things around

Rather than give up, we decided to invest in making App Router faster. I led efforts to:
  • Move more rendering to the server.
  • Reduce client bundle weight.
  • Optimize routing performance.

After several iterations, we not only recovered from the slowdown but actually surpassed our old latency metrics. Today, all major flows run on App Router, developers benefit from a modern architecture, and shoppers get a faster experience.