Dynamic Segments
Trailhead lists 40 trails, and each needs its own page: /trails/misty-ridge, /trails/fox-hollow, /trails/cedar-falls and so on. Creating 40 folders would be absurd, and every new trail would need another. One folder with a special name handles them all.
The rule
Square brackets catch the value;
paramshands it to the page.
Name a folder with square brackets and it matches any value in that position of the URL:
app/trails/[slug]/page.tsx # /trails/misty-ridge, /trails/fox-hollow, ...The word inside the brackets becomes a key in the page's params. In current Next.js, params is a promise, so you await it:
// app/trails/[slug]/page.tsx
import { notFound } from "next/navigation";
import { getTrail } from "@/lib/trails";
export default async function TrailPage({
params,
}: {
params: Promise<{ slug: string }>;
}) {
const { slug } = await params; // "misty-ridge"
const trail = await getTrail(slug);
if (!trail) notFound(); // a real 404 page
return <h1>{trail.name}</h1>;
}notFound() stops rendering and shows the 404 page, so a mistyped URL does not produce a broken page.
Two variations cover deeper URLs:
| Folder | Matches | params for /docs/a/b |
|---|---|---|
[slug] |
exactly one segment | not matched |
[...slug] |
one or more segments | { slug: ["a", "b"] } |
[[...slug]] |
zero or more segments | { slug: ["a", "b"] }, and also matches /docs |
A fixed folder beats a dynamic one at the same level: if both app/trails/new/page.tsx and app/trails/[slug]/page.tsx exist, /trails/new goes to new.
The shortcut
To predict what a page receives, line the URL up against the folders. Wherever a bracketed folder sits, the matching piece of the URL becomes the value, under the name inside the brackets.
Line them up
trails↔trails,fox-hollow↔[slug],photos↔photos.
The trap in older tutorials is reading params.slug directly without await. In current Next.js, params is a promise; await it first.
Drills
Answer fast — trust the rule.
Which folder serves every URL of the form /trails/<name>?
For /trails/cedar-falls, what does await params give the page in app/trails/[slug]/page.tsx?
Which folder matches both /guides/winter and /guides/winter/gear?
The slug matches no trail. What should the page do?
Remember
- Square brackets catch the value;
params(awaited) hands it to the page. - Shortcut: line the URL up against the folders; brackets name the value.
- Trap:
paramsis a promise in current Next.js, and a fixed folder beats a dynamic one.
