The App Router mental model that finally made Next.js click
Server Components, layouts and streaming stop being confusing the moment you stop thinking in pages and start thinking in trees.
Most people meet the App Router after years of the Pages Router, and they try to map the old model onto the new one. That mapping is exactly what makes it confusing. The App Router is not "pages with extra files" — it is a tree of nested UI, and every file convention is just a way of describing that tree.
Think in trees, not pages
A route like /portfolio/foodhut is not one page. It is a chain: the root layout wraps the portfolio layout, which wraps the project page. Each level can fetch its own data, stream independently, and re-render without touching its parents. Once you see the chain, the file conventions become obvious:
- layout.tsx — UI that survives navigation and never re-mounts
- page.tsx — the leaf that changes per URL
- loading.tsx — an automatic Suspense boundary for everything below it
- error.tsx — an error boundary scoped to one branch, not the whole app
Server Components are the default for a reason
The biggest unlock is realising that a Server Component is just a function that runs where the data lives. Fetching in the component, with caching controlled per request, removes the whole getServerSideProps / getStaticProps ceremony. My rule of thumb on this portfolio: everything is a Server Component until it needs an event handler or browser API — then, and only then, a small "use client" island.
Streaming is free performance
Because each branch is a Suspense boundary, slow data does not block fast UI. The shell paints instantly, and the project grid streams in when the API answers. On this site the public pages even fall back to bundled seed data if the API is down — the tree renders either way.
The App Router rewards a simple discipline: push data down to where it is used, keep client islands tiny, and let the tree do the work.
Enjoyed this one?
I write about what I build. Let's talk about yours.