Next.js
Next.js App Router Mental Model: Layouts, Pages, and Rendering
How to think about the App Router so layouts, pages, and server vs client rendering click into place—from first concepts to production patterns.
If you're new to Next.js or coming from the Pages Router, the App Router can feel different at first. This guide gives you a clear mental model: how routes, layouts, and server versus client rendering fit together, from beginner basics to expert-level patterns you'll use in production.
What is the App Router?
The App Router is Next.js's file-system-based routing and rendering model introduced in Next.js 13 and stable in 14+. It lives under the app/ directory. Every folder defines a segment of the URL, and special files (page.tsx, layout.tsx, loading.tsx, error.tsx) define what renders and how. Unlike the old Pages Router, the App Router is designed for React Server Components by default, so components run on the server unless you opt into client-side JavaScript with "use client".
Why it matters: A solid mental model helps you decide where to put new code, how to structure large apps, and when to use server vs client components—without guesswork.
Routes are folders and files
The URL path mirrors the folder tree under app/. One rule to remember: a folder with a page.tsx (or page.js) becomes a route. The folder name is the segment. So:
app/page.tsx→/app/about/page.tsx→/aboutapp/blog/page.tsx→/blogapp/blog/[slug]/page.tsx→/blog/hello-world,/blog/anything(dynamic segment)
A folder without a page.tsx does not create a route by itself; it's just a segment that can hold a layout and nested routes. So app/dashboard/settings/page.tsx is the page for /dashboard/settings, and app/dashboard/layout.tsx wraps both the dashboard index and settings. Layouts persist across navigations within their segment—when you move from /dashboard to /dashboard/settings, the dashboard layout stays mounted; only the page (the children) swaps.
Beginner tip: Draw a tree on paper: each folder is a node, and every page.tsx is a leaf that corresponds to a URL. That picture will save you when you add nested routes later.
Layouts wrap; pages render the segment
A layout renders once and wraps its children. It's the right place for:
- Shared UI (navigation, sidebar, footer)
- Providers that must wrap a segment (theme, auth, data cache)
- Metadata that applies to all routes in that segment
The children prop is either the next segment's layout or the page component. Pages are the leaf UI for a URL: they receive params (for dynamic segments like [slug]) and searchParams (query string) and render the main content for that route. Don't put layout-only concerns in the page; keep pages focused on that route's content so they stay easy to test and change.
// app/dashboard/layout.tsx
export default function DashboardLayout({ children }) {
return (
<div>
<DashboardNav />
<main>{children}</main>
</div>
);
}
// app/dashboard/page.tsx
export default function DashboardPage() {
return <h1>Dashboard</h1>;
}
// app/dashboard/settings/page.tsx
export default function SettingsPage() {
return <h1>Settings</h1>;
}When you visit /dashboard, you see the layout with "Dashboard" inside. When you visit /dashboard/settings, the same layout wraps "Settings". The layout does not re-render on client navigation; only the page content does.
Server and client by default
By default, every layout and page component in the App Router is a React Server Component. They run on the server: you can async/await fetch data, read the filesystem or database, and use server-only APIs. Their code is not sent to the client bundle, which keeps JavaScript smaller and improves performance.
When you need interactivity—hooks like useState, useEffect, event handlers, or browser APIs—you add "use client" at the top of the file. That file and its imports (until the next server boundary) become a Client Component. They run in the browser and ship their code to the client.
Critical rule: A Server Component cannot import a Client Component directly. It can only render a Client Component as a child or pass it as a prop (e.g. as children). So you typically have a small client "island" (e.g. a form or a chart) that a server component renders. The client component can in turn render server components as children (passed from the server). This keeps the default on the server and pushes client JS only where needed.
Expert tip: Keep the client boundary as low as possible. Put "use client" on the smallest component that needs interactivity, not on the whole page. That way you get maximum server rendering and minimal client JS.
Loading and error boundaries
loading.tsx and error.tsx are special files that wrap a segment. When a page or layout is loading (e.g. async data), Next.js shows the nearest loading.tsx (a loading UI or skeleton). When something throws, the nearest error.tsx catches it and can show a fallback and a "Try again" action. These give you predictable loading and error states without wiring them in every page.
One mental picture
Think of the tree: each segment can have a layout (wrapper) and a page (content). Layouts nest; the root layout wraps everything. Server by default; opt into client only where you need interactivity. Routes follow the folder tree; params and searchParams flow into pages. With that picture, the App Router stays easy to reason about as the app grows.
Summary
- Routes = folders +
page.tsx; the URL path mirrorsapp/. - Layouts wrap segments and persist across navigations; use them for shared UI and providers.
- Pages are the leaf content for a URL; keep them focused on that route.
- Server Components are the default; add
"use client"only where you need hooks or browser APIs. - Use loading.tsx and error.tsx for consistent loading and error handling.
With this mental model, you can structure new features and debug routing or rendering issues without confusion.