Next.js App Router: Lessons from Migrating Real Projects
Actualizado: 2026-05-03
Next.js App Router went stable in version 13.4, in May 2023. A year later, with Next.js 14.2 as the mainline, teams that took the leap have concrete lessons to share. Some cheerful, some painful, most absent from the official docs. This article collects what we keep seeing in real projects: what pays off, what costs, and how to approach the transition without torching the team’s confidence in the framework.
Key takeaways
- App Router is a mental model shift, not a syntax shift: components are server-side by default.
- Layered caching (Request Memoization, Data Cache, Full Route Cache, Router Cache) is the main source of confusion in the first month.
- The Server Component / Client Component boundary and
'use client'directive should sit as low in the tree as possible. - Server Actions eliminate API routes for simple operations; for complex flows with optimistic updates, tRPC or explicit endpoints are still better.
- For established Pages Router projects: incremental migration route by route, not a big bang. Pages Router will receive maintenance indefinitely.
A model shift, not a syntax shift
Treating App Router as “Pages Router with a different folder layout” is an expensive mistake. The new router changes the mental model of the entire application: components run on the server by default, data fetching happens with plain async/await inside the render tree, layouts nest by directory structure and preserve state across navigations, and caching becomes a layered system you must understand before debugging.
File conventions (page.tsx, layout.tsx, loading.tsx, error.tsx) replace pages/* and getServerSideProps. They aren’t optional imports — they’re hooks into the routing system with specific contracts.
On a typical detail page, the client bundle easily drops 30-40% compared to Pages Router. That translates into lower TTI, especially on mid-range mobile devices.
What really hurts
Caching is the biggest headache of the first month. Next.js caches aggressively by default. There are four cache layers, each with its own invalidation mechanism. Learning which tool to use when takes weeks of real production experience.
The Server / Client Component boundary is the second source of friction. 'use client' marks a component and all its descendants as client code; placing it too high in the tree cancels most Server Components benefits. CSS-in-JS libraries require Client Component providers. Tailwind, being compiled, doesn’t suffer the issue.
Hydration mismatches are the most painful errors. A Server Component rendering a date with the server’s timezone and a Client Component formatting with the browser’s timezone produce a mismatch React detects with a vague message. You must think explicitly about which data is stable across server and client.
Migrating without stopping the machine
Next.js supports coexistence: app/ and pages/ live in the same project. The realistic strategy is incremental. For a mid-size project with 30-60 routes, expect two to six months of actual migration work. The pattern that works best: start with read-only routes (lists, detail pages, landings), leave complex client-logic routes for last.
A common mistake is migrating “because you have to” without a plan. The technical debt of not migrating doesn’t exist yet; the debt of migrating badly does.
Deployment and performance
Numbers measured in real migrations: TTFB improves 30-50% thanks to streaming; client bundle shrinks 30-45% with good Server Component use; builds take longer and server CPU usage goes up.
App Router is a significant leap but not a free one. For new projects: start directly with App Router. For established Pages Router projects: migrate with care, route by route, without rushing, with metrics justifying each step.