---
name: nextjs-app-router-patterns
description: Applies current Next.js App Router conventions for routing, Server Components, Server Actions, caching, streaming, metadata and error handling when creating or refactoring pages and features. Use whenever writing code under an `app/` directory in Next.js 14, 15 or 16, or when migrating from the Pages Router.
license: MIT
compatibility: Next.js 14+ with the App Router. Detect the exact version from package.json because caching defaults changed in 15.
metadata:
  category: coding
  version: "1.0.0"
---

# Next.js App Router Patterns

## Before you write code

1. Read `package.json` for the `next` version. In 14, `fetch` is cached by default; in 15+ it is **not**, and `params`, `searchParams`, `cookies()` and `headers()` are async and must be awaited.
2. Read `next.config.*` for `experimental` flags (`ppr`, `dynamicIO`, `useCache`, `typedRoutes`) — they change which caching APIs are correct.
3. Look at one existing route segment to copy the file conventions already in use (`loading.tsx`, `error.tsx`, colocated `_components/`).

## Route structure

- One folder per URL segment; use route groups `(marketing)` / `(app)` to separate layouts without affecting URLs.
- Put private helpers in `_lib/` or `_components/` inside the segment so they are not treated as routes.
- Prefer `layout.tsx` for shared chrome and `template.tsx` only when you need a fresh instance per navigation.
- Add `loading.tsx` for any segment that awaits data and `error.tsx` (a Client Component) for any segment that can fail. Add `not-found.tsx` at the root and call `notFound()` for missing entities.
- Dynamic segments: `[slug]`; catch-all `[...parts]`; parallel routes `@modal` for modals that must survive refresh; intercepting routes `(.)photo/[id]` for the "open in modal, deep link to page" pattern.

## Server vs Client Components

Default to Server Components. Add `'use client'` only at the leaf that needs state, effects, browser APIs or event handlers. Rules:

- Never import a Server Component into a Client Component; pass it as `children` or a prop instead.
- Keep secrets and database clients in server-only modules and guard them with `import 'server-only'`.
- Props crossing the boundary must be serialisable (no functions, Dates become strings, no class instances).
- Read request data with `await headers()` / `await cookies()` in Server Components or actions, not in client code.

## Data fetching and caching

- Fetch in the component that needs the data; React deduplicates identical requests in one render. For non-`fetch` sources (ORMs), wrap the function with `cache()` from React.
- Parallelise independent fetches with `Promise.all`; use `<Suspense>` boundaries to stream slow parts.
- Explicitly choose a cache policy per request: `fetch(url, { next: { revalidate: 3600, tags: ['posts'] } })` or `cache: 'no-store'`. Do not rely on defaults across versions.
- Export `export const revalidate = 60` or `export const dynamic = 'force-dynamic'` at the segment level only when the whole segment needs it.
- Invalidate with `revalidateTag('posts')` or `revalidatePath('/posts')` inside the Server Action that mutated the data.
- With `dynamicIO`/`use cache` enabled (Next 15.1+/16), use the `'use cache'` directive and `cacheTag`/`cacheLife` instead of the `next` fetch options.

## Mutations with Server Actions

```ts
'use server'
import { z } from 'zod'
const Schema = z.object({ title: z.string().min(1) })
export async function createPost(prev: State, formData: FormData): Promise<State> {
  const parsed = Schema.safeParse(Object.fromEntries(formData))
  if (!parsed.success) return { errors: parsed.error.flatten().fieldErrors }
  await db.post.create({ data: parsed.data })
  revalidateTag('posts')
  redirect('/posts')
}
```

- Validate every input server-side; treat actions as public HTTP endpoints.
- Check authentication inside the action, not only in middleware.
- Use `useActionState` for form state and `useOptimistic` for instant feedback.
- `redirect()` throws; never wrap it in `try/catch`.

## Metadata, images, fonts

- Export `metadata` or `generateMetadata` from pages; include `title.template` in the root layout, `openGraph`, and `alternates.canonical`.
- Generate OG images with `opengraph-image.tsx` and `ImageResponse`.
- Load fonts with `next/font` in the root layout; never via `<link>`.
- Use `next/image` with `sizes` for responsive images and `priority` for the LCP image only.

## Middleware and auth

Keep `middleware.ts` (called `proxy.ts` in Next 16) thin: redirects, locale detection, coarse auth checks using a lightweight session cookie. Do full authorisation in the data layer.

## Checklist before finishing

- [ ] No `'use client'` in layouts or pages unless unavoidable.
- [ ] Every awaited data call has a cache decision written next to it.
- [ ] `loading.tsx` and `error.tsx` exist for the new segment.
- [ ] Server Actions validate input and check auth.
- [ ] `next build` passes with no "dynamic server usage" errors and no type errors.
- [ ] `npx next lint` clean.

## Pitfalls

- Reading `searchParams` makes the route dynamic; isolate that in a small Suspense-wrapped child.
- `useRouter` from `next/router` is the Pages Router; use `next/navigation`.
- Route Handlers (`route.ts`) should not be used for same-app mutations when a Server Action fits; use them for webhooks and third-party callers.
- Environment variables reach the client only with the `NEXT_PUBLIC_` prefix; everything else is undefined in the browser.
- `generateStaticParams` returning nothing plus `dynamicParams = false` yields 404 for every path.
