Routing through folders
Addresses come from the file tree – with a few special brackets
Every folder in app/ is one piece of the address, and a page.tsx makes it reachable. Brackets around a folder name change what the segment means.
There is no routes file to maintain. Where a page lives is its address. This lesson covers the four bracket notations you use to deviate from that.
The normal case
app/
page.tsx → /
contact/
page.tsx → /contact
blog/
page.tsx → /blog
[slug]/
page.tsx → /blog/any-value [slug] is a Dynamic segmentA folder in square brackets, `[slug]`, standing in for any value at that position.`[...all]` catches any number of levels, `[[...all]]` also covers the empty case.→: the folder name in square brackets stands in for any value. Whatever was actually in the address arrives as a parameter.
export default async function Page({
params,
}: {
params: Promise<{ slug: string }>;
}) {
const { slug } = await params;
const post = await loadPost(slug);
return <h1>{post.title}</h1>;
} Older examples online reach straight for params.slug. Since version 15 that gives you undefined or a warning. Always const { slug } = await params; first.
The four brackets
[slug] one value /blog/hello
[...path] any number /docs/a/b/c
[[...path]] or none at all /docs and /docs/a/b
(group) does NOT appear in the address The Route groupA folder in round brackets, `(marketing)`, purely for organising files – it does not appear in the address.→ in round brackets is purely organisational: app/(marketing)/pricing/page.tsx lives at /pricing, not /marketing/pricing. Handy for giving one area its own layout without changing any addresses.
app/
(marketing)/
layout.tsx ← layout for this area only
page.tsx → /
pricing/page.tsx → /pricing
(app)/
layout.tsx ← a different layout
dashboard/page.tsx → /dashboard Navigating
import Link from "next/link";
<Link href="/blog">To the blog</Link>
<Link href={`/blog/${post.slug}`}>{post.title}</Link>
// Programmatically – client components only:
"use client";
import { useRouter } from "next/navigation";
const router = useRouter();
router.push("/thanks"); next/linkAn internal link that prefetches its target in the background as soon as the link becomes visible.→ prefetches the target in the background as soon as the link becomes visible, and switches without rebuilding the page. A plain <a href> throws the whole application away and reloads it.
There are two useRouters. In the App Router it comes from next/navigation. The import from next/router belongs to the old Pages Router and throws at runtime.
What else may live in a folder
app/blog/
page.tsx the page
layout.tsx frame for /blog and everything below
loading.tsx shown while loading
error.tsx the safety net
BlogCard.tsx an ordinary component, creates NO address
utils.ts helpers, equally harmless Only the reserved names carry meaning. Everything else you can put where it is used – components do not have to migrate into a central components/ folder.
▸ In more depth: static addresses from dynamic segments optional
A page with [slug] is rebuilt on every request by default – Next.js does not know which values exist. With generateStaticParamsTells Next.js at build time which concrete values a dynamic segment should take – so it can turn them into static pages.→ you tell it, and the dynamic page turns into real static files.
export async function generateStaticParams() {
const posts = await loadAllPosts();
return posts.map((p) => ({ slug: p.slug }));
}
// Result: one finished page per post at build time. If somebody requests a slug that was not in the list, Next.js builds that page on demand and remembers it. If you do not want that, set export const dynamicParams = false – then everything else is a 404.
Does it stick?
5 questions on this lesson. Wrong answers show up in your stats.