Hirefullstack – Software Engineering & IT-Beratung aus Berlin
← Back to overview
Beginner 12 min read

Routing through folders

Addresses come from the file tree – with a few special brackets

In one sentence

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/
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.

app/blog/[slug]/page.tsx
export default async function Page({
  params,
}: {
  params: Promise<{ slug: string }>;
}) {
  const { slug } = await params;
  const post = await loadPost(slug);

  return <h1>{post.title}</h1>;
}
Since Next.js 15 params is a promise – you have to await it before looking inside.
Pitfall params and searchParams are promises

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

brackets.txt
[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.

groups.txt
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

navigation.tsx
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");
Tip Always next/link, never a plain <a>

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.

Careful next/navigation, not next/router

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

folder.txt
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.

app/blog/[slug]/page.tsx
export async function generateStaticParams() {
  const posts = await loadAllPosts();
  return posts.map((p) => ({ slug: p.slug }));
}

// Result: one finished page per post at build time.
Good to know What happens with unknown values

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.

Hirefullstack

Need React firepower on your team?

We have been building React and Next.js applications for clients across Germany for years – as a single expert, as reinforcement for an existing team, or as a complete Scrum team.

Talk about your project →