Next.js App Router Fundamentals: Routing, Layouts & Server Components
Master the Next.js App Router foundations: file-based routing, nested layouts, server vs client components, and the loading/error conventions that structure every modern Next.js app.
Prerequisites
- •Solid React basics (components, props, state)
- •Node.js 18+ installed
What You'll Learn
- Map URLs to files with the app/ directory conventions
- Compose nested layouts that preserve state across navigation
- Decide correctly between server and client components
- Add instant loading and error states with route conventions
Next.js App Router Essentials
The Mental Model
The App Router (the app/ directory) is built on one idea: your folder structure is your UI structure. Folders define URL segments; special files inside them define what renders at each segment — and React Server Components make the server the default place your components run.
npx create-next-app@latest my-app
cd my-app && npm run devFiles That Mean Something
Inside app/, a handful of filenames are conventions the router understands:
page.tsx — the UI for a route. A folder becomes a navigable URL only when it contains one.layout.tsx — shared UI that wraps every page beneath it. Layouts nest.loading.tsx — instant loading UI shown while the page's data streams in.error.tsx — an error boundary for the segment.not-found.tsx — rendered by notFound() or unmatched routes.So this tree:
app/
layout.tsx <- wraps everything (html, body, nav)
page.tsx <- /
blog/
layout.tsx <- wraps all blog pages (sidebar)
page.tsx <- /blog
[slug]/
page.tsx <- /blog/hello-world…gives you /, /blog, and /blog/anything with zero routing code.
Layouts: Persistent by Design
A layout renders its children through the children prop:
// app/blog/layout.tsx
export default function BlogLayout({ children }: { children: React.ReactNode }) {
return (
<div className="grid grid-cols-[240px_1fr] gap-8">
<BlogSidebar />
<main>{children}</main>
</div>
);
}The crucial property: navigating between /blog/post-a and /blog/post-b does not re-render the layout. Sidebar scroll position, playing audio, expanded menus — all preserved. This is why layouts, not copy-pasted wrappers in each page, are the right home for chrome.
Dynamic Segments
Square brackets capture URL parts. In current Next.js, params is a Promise — await it:
// app/blog/[slug]/page.tsx
export default async function PostPage({
params,
}: {
params: Promise<{ slug: string }>;
}) {
const { slug } = await params;
const post = await getPost(slug);
if (!post) notFound();
return <article>{/ ... /}</article>;
}[...parts] catches all remaining segments, and (groups) — parentheses — organize folders without affecting the URL (the classic use: (marketing) and (app) route groups with different root layouts).
Server Components by Default — and Why
Every component in app/ is a Server Component unless you opt out. Server components:
async and await data right in the component body// This is a complete, legitimate data-fetching component.
export default async function ProductList() {
const products = await db.product.findMany({ take: 10 });
return <ul>{products.map((p) => <li key={p.id}>{p.name}</li>)}</ul>;
}No useEffect, no loading-state juggling, no API route just to feed your own UI.
"use client" — the Interactivity Boundary
The browser still owns interactivity. When a component needs state, effects, or event handlers, mark it:
"use client";import { useState } from "react";
export function Counter() {
const [n, setN] = useState(0);
return <button onClick={() => setN(n + 1)}>Clicked {n}x</button>;
}
Two rules keep this clean:
"use client" marks a boundary, not a single file — everything that component imports becomes client code too. Put the directive on small leaf components, not on pages.children) — that's the composition pattern: server shell, client islands.Instant Loading and Error States
Drop a loading.tsx next to a page and the router shows it immediately on navigation while the server component's data resolves — powered by streaming and Suspense under the hood:
// app/blog/loading.tsx
export default function Loading() {
return <div className="animate-pulse h-40 rounded-xl bg-zinc-800" />;
}error.tsx must be a client component and receives error and a reset() retry function. Together these two files give every route professional UX with ~10 lines of code.
Linking
Use next/link for client-side navigation with automatic prefetching, and the useRouter hook (client components only) for programmatic pushes.
Next: where data actually comes from — fetch semantics, caching layers, and ISR — in Part 2: Data Fetching & Caching.

