Routing, Layouts and Navigation
Dynamic routes, nested layouts, Link and loading states.
What you will learn
- Create dynamic routes
- Nest layouts
- Use loading and not-found files
The App Router gives you a small set of special file names. Learn them and you can build almost any navigation structure.
Dynamic routes
Wrap a folder name in square brackets to capture that part of the URL as a parameter.
// app/blog/[slug]/page.tsx -> /blog/hello-world
export default async function Post({ params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params;
return <h1>Post: {slug}</h1>;
}[...slug] catches any number of segments; [[...slug]] also matches the bare path. In recent Next.js versions params is a promise, so await it.
Nested layouts
A layout.tsx in any folder wraps all pages beneath it, and layouts nest. A dashboard can have its own sidebar without affecting the marketing pages.
// app/dashboard/layout.tsx
export default function DashboardLayout({ children }: { children: React.ReactNode }) {
return (
<div style={{ display: "flex" }}>
<aside>Sidebar</aside>
<section>{children}</section>
</div>
);
}Route groups
Folders in parentheses organize code without adding to the URL: app/(marketing)/about/page.tsx is still /about. They also let different groups use different layouts.
Special files
loading.tsx: shown instantly while a page's data loads (uses React Suspense).error.tsx: shown when something inside throws. It must be a client component.not-found.tsx: shown for a missing page; callnotFound()to trigger it.route.ts: an API endpoint instead of a page.
// app/blog/[slug]/loading.tsx
export default function Loading() {
return <p>Loading post...</p>;
}
// app/blog/[slug]/page.tsx
import { notFound } from "next/navigation";
export default async function Post({ params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params;
const post = await getPost(slug); // your data function
if (!post) notFound();
return <article><h1>{post.title}</h1></article>;
}"use client";
import Link from "next/link";
import { usePathname, useRouter } from "next/navigation";
export function NavLink({ href, children }: { href: string; children: React.ReactNode }) {
const active = usePathname() === href;
return <Link href={href} className={active ? "active" : ""}>{children}</Link>;
}
export function BackButton() {
const router = useRouter();
return <button onClick={() => router.back()}>Back</button>;
}A folder without a page.tsx is not a route. You can keep components or helpers next to the pages that use them without exposing them as URLs.
Try it yourself
Create /products (a list) and /products/[id] (a detail page that shows the id), plus a loading.tsx for the detail page.
Show solution
// app/products/page.tsx
import Link from "next/link";
export default function Products() {
return <ul>{[1, 2, 3].map((id) => <li key={id}><Link href={`/products/${id}`}>Product {id}</Link></li>)}</ul>;
}
// app/products/[id]/page.tsx
export default async function Product({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params;
return <h1>Product {id}</h1>;
}