App Router Patterns

The App Router uses a file-system based routing convention inside the app/ directory. Each folder represents a route segment, and special files control rendering behavior.

File Conventions

Every route segment can include these special files:

FilePurposeRequired
page.tsxDefines the route UIYes (to make route accessible)
layout.tsxShared UI that wraps childrenNo (inherits parent layout)
loading.tsxLoading skeleton shown during navigationNo
error.tsxError boundary for the segmentNo
not-found.tsx404 UI for the segmentNo
template.tsxLike layout but re-mounts on navigationNo

Nested Layouts

Layouts persist across navigations and do not re-render. They are ideal for shared chrome like sidebars and headers.

// app/dashboard/layout.tsx
export default function DashboardLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <div className="flex">
      <aside className="w-64 border-r">
        <nav>{/* sidebar links */}</nav>
      </aside>
      <main className="flex-1 p-6">{children}</main>
    </div>
  );
}

Route Groups

Use parentheses to organize routes without affecting the URL path:

app/
├── (marketing)/
│   ├── layout.tsx      # Marketing layout
│   ├── page.tsx        # Renders at /
│   └── about/page.tsx  # Renders at /about
├── (dashboard)/
│   ├── layout.tsx      # Dashboard layout
│   └── settings/page.tsx  # Renders at /settings

Dynamic Routes

Use square brackets for dynamic segments:

// app/blog/[slug]/page.tsx
export default async function BlogPost({
  params,
}: {
  params: Promise<{ slug: string }>;
}) {
  const { slug } = await params;
  // Fetch post by slug
  return <article>{/* post content */}</article>;
}

Catch-All Routes

Use [...slug] for variable-length paths:

app/docs/[...slug]/page.tsx   →  /docs/a, /docs/a/b, /docs/a/b/c

Server vs Client Components

By default, every component in the App Router is a Server Component. Use the "use client" directive only when you need browser APIs or interactivity.

// Server Component (default) — runs on the server
export default async function Page() {
  const data = await fetch("https://api.example.com/data");
  return <div>{/* render data */}</div>;
}
// Client Component — runs in the browser
"use client";
import { useState } from "react";

export default function Counter() {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount(count + 1)}>{count}</button>;
}

Keep "use client" as low in the component tree as possible. Wrap only the interactive parts, not entire pages.