App Router vs Pages Router in 2026: where each still wins
Three years after App Router shipped, the answer is no longer 'just migrate'. Here's how I decide which router belongs on which project, and how I plan migrations.
The App Router shipped stable in May 2023. By 2026, every greenfield Next.js project I take on uses it. But for migrations, the answer is more nuanced. There are codebases where the cost of moving from Pages Router to App Router exceeds the benefit, and there are codebases where the migration is overdue. Telling them apart is part of the consulting work.
Where App Router clearly wins
Server-rendered content sites with deeply nested layouts are the cleanest App Router story. Documentation sites, marketing sites, blogs, e-commerce category pages — anywhere the layout has structure that varies by section. Pages Router's layout pattern (a single _app and _document with conditional rendering) was a workaround. App Router's nested layouts are the right primitive for the problem.
Anywhere you want server-side data fetching co-located with the component that needs it. In Pages Router, getServerSideProps lived at the page level and you passed data down through props. In App Router, a Server Component can fetch directly from the database and render the result, no prop drilling, no over-fetching to satisfy multiple downstream components.
Streaming. Pages Router could not stream. App Router streams every page by default with Suspense boundaries. For pages with slow data dependencies, this is the difference between a blank screen for two seconds and an interactive shell with skeleton loaders within 200 milliseconds.
Server Actions. Form handling without API routes. Mutations co-located with the form that triggers them. Optimistic updates with useOptimistic. The combination is genuinely simpler than the Pages Router equivalent on every project I have shipped with it.
Where Pages Router still wins
Codebases with deep getInitialProps usage, middleware that writes to the response in non-trivial ways, or custom server setups that App Router does not yet support. Migrating these is a real engineering project, not a refactor, and the benefit is unclear if the existing app is shipping fine.
Codebases that depend heavily on third-party libraries that have not adapted to React Server Components. Many older animation libraries, state management libraries, and CSS-in-JS solutions either do not work in RSC or work in awkward ways. If your codebase is a five-line patch from working in App Router, the migration is cheap. If it requires replacing your styling system, your animation library, and your data layer simultaneously, the cost is meaningful.
Sites where build time matters more than developer experience. App Router's full build is slower than Pages Router's on equivalent codebases, especially with many dynamic routes and generateStaticParams. For a site with thousands of statically generated pages on a content engine that updates frequently, the build time difference can be material.
Mental model differences
In Pages Router, a page is a page. It is rendered server-side via getServerSideProps or statically via getStaticProps, and the result is hydrated on the client. The boundary between server and client is the page level.
In App Router, a page is a tree. The leaves can be Server Components or Client Components. Server Components run only on the server and never ship JavaScript to the client. Client Components are the React components you already know. The boundary between server and client is the component level, marked by the 'use client' directive at the top of a file.
This shift sounds small and is actually large. It means you can have a Server Component that fetches data, calls into a Client Component for the interactive part, and the Client Component receives only the props it needs as serialized data. The interactive shell is small. The data is large. The split is automatic.
Common App Router pitfalls
Accidentally making everything a Client Component. The 'use client' directive is contagious — anything imported by a Client Component becomes part of the client bundle. A common mistake is to start with 'use client' at the top of a layout because it uses useState somewhere, and the entire route tree gets serialized to the client. The fix is to push 'use client' down to the leaf that actually needs interactivity, and pass data in as props from the server-side parent.
Misunderstanding caching. App Router has four overlapping caches: the router cache, the full-route cache, the data cache, and the request memoization cache. Each has different invalidation rules. The fetch API is wrapped to extend the cache with options like revalidate and tags. The result is powerful and confusing in equal measure. Read the docs once. Read them again.
Treating Server Components as a magic optimization. They are not free. Every Server Component requires a server round trip on initial render, and the streaming response has overhead. For pages with simple data and small interactive surfaces, the overhead can outweigh the benefit. The win is on pages with complex data, where shipping less JavaScript is the unambiguous goal.
How I plan migrations
Step one: audit. What pages exist, what data they fetch, which of them are content versus product versus marketing. The migration cost varies by category.
Step two: pick a starter route. The new /app directory and the existing /pages directory can coexist. Pick a route with low coupling — typically a marketing page or a documentation page — and migrate it first. Validate the build, the deployment, the analytics, the SEO. Learn the gotchas on a low-stakes page.
Step three: migrate by section. Group routes that share a layout and migrate them together. Each migration is a PR. Each PR ships independently. The router supports both directories living side by side, so partial migrations are safe.
Step four: kill the legacy directory. When the last Pages Router route is migrated, delete /pages. Update _app and _document references. Run a final integration test. Done.
Total elapsed time on a real codebase varies from two weeks for a marketing site with a dozen routes to six months for a complex SaaS app with deep getInitialProps usage and a custom server. The right benchmark is not 'how fast can we migrate' but 'is the migration paying for itself in features shipped after it lands'.
Summary
Greenfield in 2026: App Router. Existing Pages Router app shipping fine: leave it, migrate when the feature roadmap creates a natural reason. Existing Pages Router app feeling clunky to extend: budget a migration as a real engineering project, do it route by route, and ship incremental wins along the way.