5 tips for app router migration in Next.js
I recently migrated a big project from the pages to app route in Next.js. Along the way I learned some things I would like to share with the world. So here is a hopefully helpful list of tips.
1. Route by route with client components
When you want to render a component which has client interactivity or uses any React hooks you are forced to mark this component as a client component in the app router. If you are not familiar with these terms check this out. This is fine since these component can be used both in pages and app router. So you marking them with `use client` is not creating an error. But what I did was to switch one route after another to the new app router. And within that route I checked all components which needed to be marked client components. So basically go through the errors until there is none left in the route. But of course there were a lot of cases to overlook certain components which are rendered after client interaction. So you could also just mark all of them as client components and sort out the server component later. Do as you like, but try to stick to the route by route approach. In this case you can fully utilize the feature that Next.js lets you run both routers in parallel.
2. Clone and prefix app router specific files
This is a pain point of the migration and very much arguable. But for me and my company it worked out quite well.
The app router uses different hooks to get the current path. Like the following example:
Instead of the pages router approach
1import { useRouter } from 'next/router';
2
3...
4
5const { asPath } = useRouter();
you use the app router hook like this
1import { usePathname } from 'next/navigation';
So image a component uses the pathname to render a certain text related to the page you are on. Now the issue is that these hooks are each just available in their associated routers. So in pages or app router. Here I had the idea that we simply copy the components during the migration phase. This results in 2 versions and we ended up prefixing the new ones with `App-` before it's name. This was a bit of a hassle during the migration phase, because certain features needed to be implemented in both components then. Since we deployed the first migrated routes to production in order to try out the stability. But we tried to finish the migration fast so it was just a pain point for a couple of weeks. In the end this approach made the cleanup very easy since you just had to delete the non `App-` prefixed files and rename the new ones to remove the prefix.
3. Start using Turbopack
The large project had quite some issues with local compiling times. So it sometimes took up to 1 minute to compile a route when working on it locally. This was introduced when migrating the first routes. So we tried out Turbopack since it claimed to be way faster than Webpack. And they delivered, it got significantly faster after enabling it. From 50 down to 15 secs or faster.
Enable it in your package.json
1next dev --turbo
4. Leave out API routes at the beginning
Since API routes have nothing to do with all of the server component and data fetching changes in the app router you can just do them at the end of you migration. They work perfectly fine when across the 2 router types.
5. Brief Rendering Glitch in Next.js App Router: A Subtle Pitfall
I came across a subtle rendering issue that might catch other developers off guard. When using data fetching methods like fetch or database queries inside page.tsx , I noticed an unexpected behavior — the page would momentarily render blank before displaying the actual content.
This brief "flash of nothing" happened inconsistently, but enough to raise concerns, especially for pages relying heavily on dynamic data. It seems to occur because generateMetadata runs in parallel with page rendering. If metadata generation depends on the same data but doesn’t await the same fetching logic, it might lead to a mismatch in server/client timing. While working on this I did not setup the meta data at first. But this seemed like a mistake which took me hours to fix.
Lesson learned: If your metadata depends on the same data you're fetching for the page, consider moving your fetch logic into generateMetadata as well or share the fetch result between both functions to avoid redundant calls and prevent any flickering.
Even small render glitches can affect perceived performance, so understanding how Next.js handles rendering with the App Router is key to delivering a smoother user experience.