Skip to main content
CodeOath
← All posts

Next.js75 min total · 12 parts

Next.js Fundamentals: The App Router, Server Components, and the Caching Rules That Just Flipped

Part 3 of 12 · ~3 min

App Router vs Pages Router

Search "Next.js routing" and you'll land on documentation, and older job postings, describing two entirely separate systems: the App Router (the app/ folder, the current default, and what everything below is built on) and the Pages Router (the pages/ folder, which is how essentially every Next.js app was built before the App Router existed, and plenty still are).

The App Router isn't change for its own sake. The Pages Router ran into a structural wall it genuinely couldn't get past on its own. A page there is fundamentally one component, rendered once, shipping its JavaScript to the browser regardless — there was no way to say "this branch of the tree should never touch the browser at all." Fetching lived in one function attached to the whole page (getServerSideProps, getStaticProps), so a component three levels deep that needed data of its own couldn't just go get it — every request funneled through that single top-level function first, no matter how deep the actual need for the data sat. And a page was slow as one unit: the browser saw nothing until every piece of data that function asked for had come back, fast piece or slow.

Three capabilities the Pages Router had no way to offer are what the App Router is actually built around:

  • React Server Components, which can run entirely on the server and reach for their own data wherever they sit in the tree, instead of every request having to pass through one function at the top.
  • Layouts that nest, so shared UI wraps a subtree of routes and stays put across navigation inside it, rather than being rebuilt from scratch on every page.
  • Streaming, so a response can send its ready parts immediately and fill in the slow parts afterward, instead of one slow data source holding the whole page hostage.

None of that retires the Pages Router. You'll keep meeting it, and there are honest reasons to reach for it on purpose:

  • A large app that's already built on it. Rewriting a working app's entire routing and data layer is a genuinely risky project. Plenty of teams never fully migrate, and plenty of profitable apps will keep running the Pages Router for years.
  • A flatter model for an app that doesn't need Server Components. Getting a plain object of props back from one function is just an easier thing to hold in your head than sorting out, component by component, which half of the tree is even allowed to touch a browser API. A small app that isn't juggling much data has little to gain from the extra machinery, and picking the simpler router there is a sound call, not a corner cut.
Pages RouterApp Router
Where it livespages/app/
Getting data inOne exported function per page — getServerSideProps, getStaticPropsAny Server Component can await fetch(...) (or any async call) on its own
Server-only code as a conceptDoesn't really existThe default — every Server Component is server-only
Shared layoutHand-built, usually a custom _app.js every page passes throughlayout.tsx files scoped to a folder, nesting naturally
Sending partial responses earlyNoYes, through Suspense
Where you'll run into itLegacy codebases, older tutorials, take-home interview reposNew projects — OpenRoles, and everything below