Understanding the Next.js App Router
The App Router is the biggest change to Next.js in years. Here is a practical mental model for how it actually works.
The App Router landed in Next.js 13 and has been stable since v14. If you've been putting off learning it, this post will give you a practical mental model for how it works and why it's worth the switch.
The fundamental shift: React Server Components
The App Router is built on React Server Components (RSC). This changes the default:
| Pages Router | App Router | |
|---|---|---|
| Default | Client Component | Server Component |
| Data fetching | getServerSideProps / getStaticProps | async component functions |
| Client interactivity | Whole page | Opt-in with "use client" |
Server Components run on the server, can await data, and send only HTML to the browser. No JavaScript bundle. No hydration. Just content.
The file conventions
The App Router uses a folder-based convention:
app/
layout.tsx # Wraps all children — persists on navigation
page.tsx # The route itself
loading.tsx # Suspense fallback
error.tsx # Error boundary
not-found.tsx # 404 handler
blog/
page.tsx # /blog
[slug]/
page.tsx # /blog/:slug
When to use "use client"
You only need "use client" when you use:
- React state (useState, useReducer)
- React effects (useEffect)
- Browser APIs (window, document)
- Event handlers (onClick, onChange)
Everything else can — and should — stay on the server.
Nested layouts
The layout.tsx wraps its segment's subtree and persists across navigations in that segment. This means the navbar and sidebar don't re-render when you navigate between blog posts. Only the content changes.
Data fetching
// app/blog/[slug]/page.tsx
export default async function PostPage({ params }: { params: { slug: string } }) {
const post = await fetchPost(params.slug); // runs on the server
return <article>{post.content}</article>;
}
That's it. No getStaticProps, no useEffect, no loading state dance. Just async/await.
Conclusion
The App Router takes some getting used to, but once the mental model clicks — "Server by default, client by opt-in" — it's hard to go back. Start a new project with it and you'll see immediately how much simpler data fetching and layout composition become.