# Next.js Routing (App Router)

## 1. The Router Is the File System

There's no route-definition file to maintain — the folder structure under `app/` **is** the route table. A folder maps to a URL segment; a `page.tsx` inside it makes that segment navigable.

```
app/
  page.tsx              → /
  about/page.tsx         → /about
  blog/page.tsx           → /blog
  blog/[slug]/page.tsx     → /blog/:slug   (dynamic segment)
```

## 2. Dynamic Segments

```tsx
// app/blog/[slug]/page.tsx
export default async function BlogPost({
  params,
}: {
  params: Promise<{ slug: string }>;
}) {
  const { slug } = await params;
  return <h1>Post: {slug}</h1>;
}
```

`[slug]` captures one path segment. Visiting `/blog/hello-world` renders this page with `params.slug === "hello-world"`. In current Next.js versions, `params` is a `Promise` — it must be awaited (a change from earlier versions where it was a plain object), because Next.js needs to support cases where the value isn't known synchronously.

## 3. Catch-All and Optional Catch-All Segments

```
app/docs/[...slug]/page.tsx      → /docs/a, /docs/a/b, /docs/a/b/c ...  (one or more segments)
app/docs/[[...slug]]/page.tsx    → /docs, /docs/a, /docs/a/b ...        (zero or more segments)
```

```tsx
// app/docs/[...slug]/page.tsx
export default async function Docs({ params }: { params: Promise<{ slug: string[] }> }) {
  const { slug } = await params; // e.g. ["a", "b", "c"] for /docs/a/b/c
  return <p>Path: {slug.join("/")}</p>;
}
```

## 4. Special Files Per Segment

| File | Purpose |
|---|---|
| `page.tsx` | the route's unique UI, makes the segment public |
| `layout.tsx` | shared UI that wraps this segment and its children, persists across navigations within it |
| `loading.tsx` | instant loading UI, shown via React Suspense while the segment's data loads |
| `error.tsx` | error boundary for this segment (must be a Client Component) |
| `not-found.tsx` | rendered when `notFound()` is called or no route matches |
| `route.ts` | an API endpoint (Route Handler) instead of a page, for this segment |

```tsx
// app/blog/loading.tsx
export default function Loading() {
  return <p>Loading posts...</p>;
}
```

```tsx
// app/blog/error.tsx
"use client";

export default function Error({ error, reset }: { error: Error; reset: () => void }) {
  return (
    <div>
      <p>Something went wrong: {error.message}</p>
      <button onClick={() => reset()}>Try again</button>
    </div>
  );
}
```

Next.js automatically wraps a segment's `page.tsx` in a `<Suspense>` boundary using its sibling `loading.tsx` as the fallback, and in an error boundary using `error.tsx` — you don't write the boundary components yourself.

## 5. Route Groups — Organize Without Affecting the URL

```
app/
  (marketing)/
    about/page.tsx    → /about   (the "(marketing)" segment is invisible in the URL)
    contact/page.tsx    → /contact
  (shop)/
    products/page.tsx    → /products
```

Parentheses around a folder name (`(marketing)`) create a **route group**: it organizes files and can hold its own `layout.tsx` applying only to routes inside it, but contributes nothing to the URL path.

## 6. Parallel Routes and Intercepting Routes (Advanced)

```
app/
  @analytics/page.tsx
  @team/page.tsx
  layout.tsx
```

```tsx
// app/layout.tsx
export default function Layout({
  children,
  analytics,
  team,
}: {
  children: React.ReactNode;
  analytics: React.ReactNode;
  team: React.ReactNode;
}) {
  return (
    <>
      {children}
      {analytics}
      {team}
    </>
  );
}
```

A `@folder` is a **parallel route slot** — the layout receives it as a named prop and can render multiple independent, simultaneously-active pages in the same layout (dashboards with independently loading widgets are the canonical use case).

## 7. Navigating Between Routes

```tsx
import Link from "next/link";

<Link href="/about">About</Link>;
```

```tsx
"use client";
import { useRouter } from "next/navigation";

function BackButton() {
  const router = useRouter();
  return <button onClick={() => router.back()}>Go back</button>;
}
```

`<Link>` is the primary navigation primitive — it prefetches the target route's code (and, for static routes, its rendered payload) when it scrolls into view, and performs a client-side transition instead of a full page reload. `useRouter()` (from `next/navigation`, not the old `next/router`) gives imperative navigation (`push`, `replace`, `back`, `refresh`) for use inside event handlers.

## Under the Hood

**The route tree is compiled into a nested React composition, not resolved dynamically per request.** During `next build`, Next.js walks `app/` and builds an actual **tree of React elements** where each `layout.tsx` wraps its child segment's `layout.tsx`/`page.tsx`. For a URL like `/blog/hello-world` matching `app/layout.tsx → app/blog/layout.tsx → app/blog/[slug]/page.tsx`, the real rendered tree is:

```tsx
<RootLayout>
  <BlogLayout>
    <BlogPostPage params={{ slug: "hello-world" }} />
  </BlogLayout>
</RootLayout>
```

This is genuine React parent/child composition — a `layout.tsx` receiving `children` and rendering it is just a component receiving a prop, exactly like any other composed React tree.

![the file-system route tree for /blog/hello-world compiles into nested React elements: RootLayout wraps BlogLayout wraps BlogPostPage](diagrams/01-route-tree-composition.svg) The "route matching" step at request time is really just: match the URL to a leaf `page.tsx`, then walk back up the folder tree collecting every ancestor `layout.tsx` to build this nested element tree before rendering it (server-side, as covered in the Server Components topic). This is also *why* layouts don't re-render on navigation between sibling routes that share them — React's reconciliation sees the same component instance in the same tree position and preserves it, exactly as it would for any unchanged parent in a normal React update, rather than remounting shared chrome on every navigation.

**`loading.tsx` is sugar for `<Suspense fallback={<Loading />}>` wrapped around the segment.** Next.js doesn't invent a new async-boundary primitive — it uses React's real `Suspense` component, automatically inserting it into the composed tree above the segment that has a sibling `loading.tsx`. When the async Server Component for that segment suspends (its promise hasn't resolved), React's existing Suspense mechanism shows the fallback and swaps to the real content when the promise resolves — the same mechanism `React.lazy` and `use()` rely on elsewhere in React, not something routing-specific.

**`error.tsx` must be a Client Component because React error boundaries are a class-component feature under the hood, and the boundary needs interactive recovery.** Next.js's `error.tsx` convention is compiled into a real React error boundary wrapping the segment. Error boundaries fundamentally require the `componentDidCatch`/`getDerivedStateFromError` class lifecycle — there is no hook-based equivalent in React itself — so `error.tsx` needs `"use client"` both to run that boundary logic in the browser and to support the `reset()` callback it's handed, which re-renders the segment's subtree by remounting it.

## Common Pitfalls

1. **Forgetting `params` is a Promise in current versions** — `params.slug` directly (without `await`) either errors or silently gives you a Promise object instead of the string; always `await params` (or `use(params)` in a Client Component) first.
2. **Putting `page.tsx` where you meant a layout-only route group** — any folder with a `page.tsx` becomes a real, navigable URL segment; use `(groupName)` parentheses if you only want organization/shared layout without a URL segment.
3. **Expecting `error.tsx` to catch errors in its own parent layout** — an error boundary only catches errors from its children, so an error thrown inside the layout above `error.tsx` propagates further up to the nearest ancestor `error.tsx` instead.
4. **Overusing catch-all routes for what should be separate pages** — `[...slug]` is powerful but pushes all matching logic into runtime code (usually a CMS lookup); prefer explicit segments when the set of routes is known ahead of time.
5. **Not providing a `loading.tsx`** and being surprised navigation feels like it "hangs" — without it, the whole segment (and everything under it) waits for all data before showing anything, instead of streaming a fallback immediately.

## Quick Reference

```
app/page.tsx                    → /
app/about/page.tsx               → /about
app/blog/[slug]/page.tsx          → /blog/:slug        (params.slug)
app/docs/[...slug]/page.tsx        → /docs/a/b/c ...     (params.slug: string[])
app/docs/[[...slug]]/page.tsx       → /docs, /docs/a ...  (optional catch-all)
app/(group)/...                      → organizational only, no URL segment
app/@slot/page.tsx                    → parallel route, injected as a named layout prop
layout.tsx / loading.tsx / error.tsx / not-found.tsx / route.ts  → per-segment special files
<Link href="...">                      → prefetching client-side navigation
useRouter() from "next/navigation"      → imperative navigation (push/replace/back/refresh)
```
