After this lesson, you will be able to: Build a multi-page Next.js app using file-based routing, share UI across routes with nested layouts, handle slow data with `loading.tsx`, and gracefully recover from crashes with `error.tsx` — all without writing a single line of router config.
The App Router is what makes Next.js feel like a framework instead of a library. You don't configure routes; you create files. You don't build loading states; you create `loading.tsx`. You don't write error boundaries; you create `error.tsx`. This lesson takes you from zero understanding of Next.js routing to fluent on the four special files every page needs.
In `app/`, every folder is a URL segment. Every folder that contains a `page.tsx` becomes a real page. `app/page.tsx` → `/`. `app/about/page.tsx` → `/about`. `app/blog/[slug]/page.tsx` → `/blog/anything-here`. That's the whole routing system. No `router.config.js`, no `<Route>` components, no string-based path matching. The file system IS the routing table.
Create the folders and files below. Within minutes you have a three-page app. Notice that the folder name becomes the URL.
app/├── page.tsx // /├── about/│ └── page.tsx // /about├── blog/│ ├── page.tsx // /blog│ └── [slug]/│ └── page.tsx // /blog/anything└── layout.tsx // wraps every page// app/blog/[slug]/page.tsxexport default function BlogPost({params,}: {params: Promise<{ slug: string }>;}) {// In Next 15+, params is a Promise — await it inside an async server component.return <BlogPostInner params={params} />;}async function BlogPostInner({params,}: {params: Promise<{ slug: string }>;}) {const { slug } = await params;return (<article><h1>Post: {slug}</h1></article>);}
A `layout.tsx` wraps every page below it in the same folder. Put your navbar, footer, and global wrappers here once instead of in every page.
// app/layout.tsx — the root layout (REQUIRED)export const metadata = {title: "My Site",};export default function RootLayout({children,}: {children: React.ReactNode;}) {return (<html lang="en"><body><header className="border-b p-4"><nav className="flex gap-4"><a href="/">Home</a><a href="/about">About</a><a href="/blog">Blog</a></nav></header><main>{children}</main></body></html>);}// app/blog/layout.tsx — a nested layout, ONLY applies to /blog/*export default function BlogLayout({children,}: {children: React.ReactNode;}) {return (<section className="max-w-3xl mx-auto py-8">{children}</section>);}
Drop a `loading.tsx` next to a `page.tsx` and Next.js shows it automatically while the page is fetching. Drop an `error.tsx` and Next.js catches any thrown error and shows the fallback. Zero router config.
// app/blog/loading.tsx — shown while /blog/* is renderingexport default function Loading() {return <p className="p-8">Loading posts...</p>;}// app/blog/error.tsx — shown if any page in /blog/* throws"use client"; // error boundaries MUST be client componentsexport default function ErrorBoundary({error,reset,}: {error: Error;reset: () => void;}) {return (<div className="p-8"><h2 className="text-xl font-semibold">Something went wrong</h2><p className="text-slate-600 mt-2">{error.message}</p><buttononClick={reset}className="mt-4 rounded bg-slate-900 px-4 py-2 text-white">Try again</button></div>);}// app/blog/[slug]/not-found.tsx — shown when notFound() is calledexport default function NotFound() {return <p className="p-8">No post by that slug.</p>;}
Use the `<Link>` component from `next/link`, not raw `<a>` tags, for in-app navigation. `<Link>` does client-side routing (no full page reload), prefetches the destination on hover, and integrates with Next.js's loading states. Reserve `<a href>` for links that leave your site (external URLs, file downloads).
One import, one component, no surprises.
import Link from "next/link";export default function Nav() {return (<nav className="flex gap-4"><Link href="/">Home</Link><Link href="/blog" className="font-semibold">Blog</Link><Link href="/blog/hello-world">Hello post</Link><a href="https://nextjs.org" target="_blank" rel="noopener">Docs (external)</a></nav>);}
In `app/`, you have a homepage at `app/page.tsx`. Add an `app/about/page.tsx` that exports a default `About()` function returning a heading and a paragraph. Then update the root layout `app/layout.tsx` to include a `<Link href="/about">About</Link>` in its `<nav>`. Required patterns check for the `About` component, the Link import, and the new nav entry.
Sign in and purchase access to unlock this lesson.