App Router landed in Next.js 13 and became stable in 13.4 after months of "experimental" warnings that kept everyone away from production. It's basically a complete rewrite of how Next.js routing works, built around React Server Components - components that run on the server instead of the browser.
This isn't just a routing change - React itself works differently now. Dan Abramov explained why they built this, but honestly the migration pain is real.
How File-System Routing Actually Works
The app directory works differently than pages - here's how
Instead of a single pages/
directory, you now work with an app/
directory where folders define routes and special files control behavior:
page.tsx
- makes a route publicly accessiblelayout.tsx
- shared UI that wraps child pagesloading.tsx
- instant loading UI shown during navigationerror.tsx
- error boundaries for that route segment
The biggest gotcha? Everything runs on the server by default. No more useEffect
, useState
, or browser APIs unless you add 'use client'
at the top of your file. Spent way too long debugging some localhost issue with useless error messages like 'undefined' that don't mention server-side rendering.
You'll see this error constantly during migration: "Cannot use useState in Server Component". The migration guide makes it sound easy, but GitHub discussions are full of developers pulling their hair out.
Server Components: The Mental Model Shift
Server Components run on the server and send HTML to the browser. They can't use hooks, event handlers, or browser APIs, but they can directly access your database or file system.
The trade-off is real: smaller bundles and faster loads, but you'll constantly hit walls like "Cannot use useState in Server Component" errors. The docs help, but expect to add 'use client'
more than you'd like.
Server Components work great for static content and data fetching. For interactive UIs, you'll be back to Client Components anyway. Josh Comeau's guide explains this better than the official docs.
Migration is a complete shitshow for some teams, others get lucky. Some teams love the performance gains, others hit auth issues and went back to Vite. The time estimates in docs are complete bullshit - real migrations take 3x longer, especially with third-party integrations.
Data Fetching: Goodbye getServerSideProps
App Router ditches getServerSideProps
and getStaticProps
for direct fetch()
calls in components. The migration docs make it sound simple, but there are gotchas:
// Old Pages Router way
export async function getServerSideProps() {
const data = await fetch('...')
return { props: { data } }
}
// New App Router way
async function Page() {
const data = await fetch('...', { next: { revalidate: 60 } })
return <div>{data.title}</div>
}
The catch: Error handling is completely different and will bite you in production. With getServerSideProps
, errors got caught and handled predictably. Now if your fetch fails, you need proper error boundaries or your entire route blows up. Learned this hard way when our database went down and users saw "ENOTFOUND postgres.internal".
Good news: Request deduplication works automatically - make the same fetch call in 3 components and Next.js only makes one network request.
Performance: It's Complicated
App Router can be faster, but GitHub discussions show mixed results. The caching system has 4 layers that even experienced developers find confusing:
- Router Cache (client-side)
- Full Route Cache (server-side static HTML)
- Request Memoization (during render)
- Data Cache (persistent fetch cache)
The caching system is so convoluted that senior devs just disable half of it. Many teams end up disabling parts of it during debugging because it's faster than figuring out why data isn't updating. Flightcontrol's migration post covers performance wins and losses from production migrations.
Migration Reality Check
GitHub is full of migration issues - authentication breaking, routing problems, performance regressions. The official migration guide suggests gradual migration, but developers report auth handlers breaking and i18n routing problems.
That said, once you get through the initial pain, the routing system is actually cleaner than Pages Router. I complained about it earlier, but honestly the file structure makes more sense once your brain stops fighting it.
Bottom line for 2025: App Router works well for new projects. For existing apps, budget extra time beyond what the docs suggest. Hot reload can be flaky - restart the dev server whenever weird stuff happens. Module resolution occasionally acts up but usually fixes itself with a restart.