CodeOath
← All posts
Next.js31 min total · 12 parts

Next.js Fundamentals: The App Router, Server Components, and the Caching Layer Nobody Warns You About

Part 3 of 12 · ~3 min

App Router vs Pages Router

Next.js currently ships two entirely different routing systems side by side: the App Router (the app/ directory, the current default and the one this whole reference focuses on) and the older Pages Router (the pages/ directory, the only router Next.js had for years). Knowing both matters less for writing new code than for reading it — a huge amount of Next.js code in production, in tutorials, and in interview take-homes still predates the App Router entirely.

The App Router exists because the Pages Router hit a real architectural ceiling. Every page in the Pages Router is, at bottom, a component that renders once and ships its JavaScript to the browser — there's no built-in notion of "this part of the tree never needs to run in the browser at all." Data fetching lived in special exported functions (getServerSideProps, getStaticProps) attached to the page as a whole, which meant a deeply nested component that needed its own data had no way to fetch it independently — everything funneled up through the top-level page function first. And a slow page was slow all at once: the whole page waited on the whole data-fetching function before the browser saw anything.

The App Router rebuilds routing around three ideas the Pages Router structurally couldn't express:

  • React Server Components — components that run only on the server and can fetch their own data directly, at whatever level of nesting they live at, instead of routing everything through one top-level function.
  • Nested layouts — UI that wraps a whole subtree of routes and persists across navigation within it, rather than being recomputed from scratch as part of each page.
  • Streaming — the server can send the parts of a page that are ready immediately and fill in slower parts later, instead of holding the entire response until everything has resolved.

None of that makes the Pages Router obsolete or wrong to use. You'll still run into it, and honestly still choose it, in a few real situations:

  • An existing large codebase already built on it. Migrating a mature app's routing and data-fetching model wholesale is a significant, genuinely risky undertaking — most teams migrate incrementally, if at all, and plenty of profitable production apps will run the Pages Router for years.
  • A simpler mental model for a team that doesn't need Server Components. getServerSideProps returning a plain props object is a flatter, more predictable mental model than "some components run on the server, some in the browser, and the boundary between them is a directive you place yourself." For a small app with modest data needs, that simplicity is a legitimate trade-off, not a compromise.
Pages RouterApp Router
Directorypages/app/
Data fetchinggetServerSideProps / getStaticProps, one per pagefetch (or any async call) directly inside any Server Component
Server-only codeNot a first-class conceptServer Components, by default
LayoutsManual, via a custom _app.js wrapping every pageNested layout.tsx files, scoped per route segment
StreamingNot supported nativelyBuilt in, via Suspense boundaries
Where you'll meet itExisting apps, older tutorialsNew projects, this reference