)
}
```
**See Template**: `templates/cache-component-use-cache.tsx`
---
### 3. Partial Prerendering (PPR)
**PPR** allows caching static parts of a page while rendering dynamic parts on-demand.
**Pattern**:
```typescript
// app/dashboard/page.tsx
// Static header (cached)
'use cache'
async function StaticHeader() {
return My App
}
// Dynamic user info (not cached)
async function DynamicUserInfo() {
const cookieStore = await cookies()
const userId = cookieStore.get('userId')?.value
const user = await fetch(`/api/users/${userId}`).then(r => r.json())
return
Welcome, {user.name}
}
// Page combines both
export default function Dashboard() {
return (
{/* Cached */}
{/* Dynamic */}
)
}
```
**When to Use PPR**:
- Page has both static and dynamic content
- Want to cache layout/header/footer but render user-specific content
- Need fast initial load (static parts) + personalization (dynamic parts)
**See Reference**: `references/cache-components-guide.md`
---
### 4. `revalidateTag()` - Updated API
**BREAKING CHANGE**: `revalidateTag()` now requires a **second argument** (`cacheLife` profile) for stale-while-revalidate behavior.
**Before (Next.js 15)**:
```typescript
import { revalidateTag } from 'next/cache'
export async function updatePost(id: string) {
await fetch(`/api/posts/${id}`, { method: 'PATCH' })
revalidateTag('posts') // ❌ Only one argument in Next.js 15
}
```
**After (Next.js 16)**:
```typescript
import { revalidateTag } from 'next/cache'
export async function updatePost(id: string) {
await fetch(`/api/posts/${id}`, { method: 'PATCH' })
revalidateTag('posts', 'max') // ✅ Second argument required in Next.js 16
}
```
**Built-in Cache Life Profiles**:
- `'max'` - Maximum staleness (recommended for most use cases)
- `'hours'` - Stale after hours
- `'days'` - Stale after days
- `'weeks'` - Stale after weeks
- `'default'` - Default cache behavior
**Custom Cache Life Profile**:
```typescript
revalidateTag('posts', {
stale: 3600, // Stale after 1 hour (seconds)
revalidate: 86400, // Revalidate every 24 hours (seconds)
expire: false, // Never expire (optional)
})
```
**Pattern in Server Actions**:
```typescript
'use server'
import { revalidateTag } from 'next/cache'
export async function createPost(formData: FormData) {
const title = formData.get('title') as string
const content = formData.get('content') as string
await fetch('/api/posts', {
method: 'POST',
body: JSON.stringify({ title, content }),
})
revalidateTag('posts', 'max') // ✅ Revalidate with max staleness
}
```
**See Template**: `templates/revalidate-tag-cache-life.ts`
---
### 5. `updateTag()` - NEW API (Server Actions Only)
**NEW in Next.js 16**: `updateTag()` provides **read-your-writes semantics** for Server Actions.
**What it does**:
- Expires cache immediately
- Refreshes data within the same request
- Shows updated data right after mutation (no stale data)
**Difference from `revalidateTag()`**:
- `revalidateTag()`: **Stale-while-revalidate** (shows stale data, revalidates in background)
- `updateTag()`: **Immediate refresh** (expires cache, fetches fresh data in same request)
**Use Case**: Forms, user settings, or any mutation where user expects immediate feedback.
**Pattern**:
```typescript
'use server'
import { updateTag } from 'next/cache'
export async function updateUserProfile(formData: FormData) {
const name = formData.get('name') as string
const email = formData.get('email') as string
// Update database
await db.users.update({ name, email })
// Immediately refresh cache (read-your-writes)
updateTag('user-profile')
// User sees updated data immediately (no stale data)
}
```
**When to Use**:
- **`updateTag()`**: User settings, profile updates, critical mutations (immediate feedback)
- **`revalidateTag()`**: Blog posts, product listings, non-critical updates (background revalidation)
**See Template**: `templates/server-action-update-tag.ts`
---
### 6. `refresh()` - NEW API (Server Actions Only)
**NEW in Next.js 16**: `refresh()` refreshes **uncached data only** (complements client-side `router.refresh()`).
**When to Use**:
- Refresh dynamic data without affecting cached data
- Complement `router.refresh()` on server side
**Pattern**:
```typescript
'use server'
import { refresh } from 'next/cache'
export async function refreshDashboard() {
// Refresh uncached data (e.g., real-time metrics)
refresh()
// Cached data (e.g., static header) remains cached
}
```
**Difference from `revalidateTag()` and `updateTag()`**:
- `refresh()`: Only refreshes **uncached** data
- `revalidateTag()`: Revalidates **specific tagged** data (stale-while-revalidate)
- `updateTag()`: Immediately expires and refreshes **specific tagged** data
**See Reference**: `references/cache-components-guide.md`
---
## Server Components
Server Components are React components that render on the server. They enable efficient data fetching, reduce client bundle size, and improve performance.
### 1. Server Component Basics
**Default Behavior**: All components in the App Router are Server Components by default (unless marked with `'use client'`).
**Example**:
```typescript
// app/posts/page.tsx (Server Component by default)
export default async function PostsPage() {
const posts = await fetch('https://api.example.com/posts').then(r => r.json())
return (
}>
{/* Streams when ready */}
)
}
```
**When to Use Streaming**:
- Page has slow API calls
- Want to show UI immediately (don't wait for all data)
- Improve perceived performance
**See Reference**: `references/server-components-patterns.md`
---
### 4. Server vs Client Components
**When to Use Server Components** (default):
- Fetch data from APIs/databases
- Access backend resources (files, environment variables)
- Keep large dependencies on server (reduce bundle size)
- Render static content
**When to Use Client Components** (`'use client'`):
- Need React hooks (`useState`, `useEffect`, `useContext`)
- Need event handlers (`onClick`, `onChange`, `onSubmit`)
- Need browser APIs (`window`, `localStorage`, `navigator`)
- Need third-party libraries that use browser APIs (charts, maps, etc.)
**Pattern**: Use Server Components by default, add `'use client'` only when needed.
```typescript
// app/components/interactive-button.tsx
'use client' // Client Component
import { useState } from 'react'
export function InteractiveButton() {
const [count, setCount] = useState(0)
return (
)
}
// app/page.tsx (Server Component)
import { InteractiveButton } from './components/interactive-button'
export default async function Page() {
const data = await fetch('/api/data').then(r => r.json())
return (
{data.title}
{/* Client Component inside Server Component */}
)
}
```
**Composition Rules**:
- ✅ Server Component can import Client Component
- ✅ Client Component can import Client Component
- ✅ Client Component can render Server Component as children (via props)
- ❌ Client Component cannot import Server Component directly
**See Reference**: `references/server-components-patterns.md`
---
## Server Actions
Server Actions are asynchronous functions that run on the server. They enable server-side mutations, form handling, and data revalidation.
### 1. Server Action Basics
**Definition**: Add `'use server'` directive to create a Server Action.
**File-level Server Actions**:
```typescript
// app/actions.ts
'use server'
export async function createPost(formData: FormData) {
const title = formData.get('title') as string
const content = formData.get('content') as string
// Mutate database
await db.posts.create({ title, content })
// Revalidate cache
revalidateTag('posts', 'max')
}
```
**Inline Server Actions**:
```typescript
// app/posts/new/page.tsx
export default function NewPostPage() {
async function createPost(formData: FormData) {
'use server'
const title = formData.get('title') as string
const content = formData.get('content') as string
await db.posts.create({ title, content })
revalidateTag('posts', 'max')
}
return (
)
}
```
**See Template**: `templates/server-actions-form.tsx`
---
### 2. Form Handling
**Basic Form**:
```typescript
// app/components/create-post-form.tsx
import { createPost } from '@/app/actions'
export function CreatePostForm() {
return (
)
}
```
**With Loading State** (useFormStatus):
```typescript
'use client'
import { useFormStatus } from 'react-dom'
import { createPost } from '@/app/actions'
function SubmitButton() {
const { pending } = useFormStatus()
return (
)
}
export function CreatePostForm() {
return (
)
}
```
**With Validation**:
```typescript
// app/actions.ts
'use server'
import { z } from 'zod'
import { redirect } from 'next/navigation'
const PostSchema = z.object({
title: z.string().min(3, 'Title must be at least 3 characters'),
content: z.string().min(10, 'Content must be at least 10 characters'),
})
export async function createPost(formData: FormData) {
const rawData = {
title: formData.get('title'),
content: formData.get('content'),
}
// Validate
const parsed = PostSchema.safeParse(rawData)
if (!parsed.success) {
return {
errors: parsed.error.flatten().fieldErrors,
}
}
// Mutate
await db.posts.create(parsed.data)
// Revalidate and redirect
revalidateTag('posts', 'max')
redirect('/posts')
}
```
**See Template**: `templates/server-actions-form.tsx`
**See Reference**: `references/server-actions-guide.md`
---
### 3. Error Handling
**Pattern**: Return error objects from Server Actions, handle in Client Components.
**Server Action**:
```typescript
// app/actions.ts
'use server'
export async function deletePost(id: string) {
try {
await db.posts.delete({ where: { id } })
revalidateTag('posts', 'max')
return { success: true }
} catch (error) {
return {
success: false,
error: 'Failed to delete post. Please try again.'
}
}
}
```
**Client Component**:
```typescript
'use client'
import { useState } from 'react'
import { deletePost } from '@/app/actions'
export function DeleteButton({ postId }: { postId: string }) {
const [error, setError] = useState(null)
async function handleDelete() {
const result = await deletePost(postId)
if (!result.success) {
setError(result.error)
}
}
return (
{error &&
{error}
}
)
}
```
---
### 4. Optimistic Updates
**Pattern**: Show UI update immediately, then sync with server.
```typescript
'use client'
import { useOptimistic } from 'react'
import { likePost } from '@/app/actions'
export function LikeButton({ postId, initialLikes }: { postId: string; initialLikes: number }) {
const [optimisticLikes, addOptimisticLike] = useOptimistic(
initialLikes,
(state, amount: number) => state + amount
)
async function handleLike() {
// Update UI immediately
addOptimisticLike(1)
// Sync with server
await likePost(postId)
}
return (
)
}
```
**See Reference**: `references/server-actions-guide.md`
---
## Route Handlers
Route Handlers are server-side API endpoints in the App Router. They replace API Routes from the Pages Router.
### 1. Basic Route Handler
**File**: `app/api/hello/route.ts`
```typescript
import { NextResponse } from 'next/server'
export async function GET() {
return NextResponse.json({ message: 'Hello, World!' })
}
export async function POST(request: Request) {
const body = await request.json()
return NextResponse.json({
message: 'Post created',
data: body
})
}
```
**Supported Methods**: `GET`, `POST`, `PUT`, `PATCH`, `DELETE`, `HEAD`, `OPTIONS`
**See Template**: `templates/route-handler-api.ts`
---
### 2. Dynamic Routes
**File**: `app/api/posts/[id]/route.ts`
```typescript
import { NextResponse } from 'next/server'
export async function GET(
request: Request,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params // ✅ Await params in Next.js 16
const post = await db.posts.findUnique({ where: { id } })
if (!post) {
return NextResponse.json(
{ error: 'Post not found' },
{ status: 404 }
)
}
return NextResponse.json(post)
}
export async function DELETE(
request: Request,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params
await db.posts.delete({ where: { id } })
return NextResponse.json({ message: 'Post deleted' })
}
```
---
### 3. Search Params
**URL**: `/api/posts?tag=javascript&limit=10`
```typescript
import { NextResponse } from 'next/server'
export async function GET(request: Request) {
const { searchParams } = new URL(request.url)
const tag = searchParams.get('tag')
const limit = parseInt(searchParams.get('limit') || '10')
const posts = await db.posts.findMany({
where: { tags: { has: tag } },
take: limit,
})
return NextResponse.json(posts)
}
```
---
### 4. Webhooks
**Pattern**: Handle incoming webhooks from third-party services.
```typescript
// app/api/webhooks/stripe/route.ts
import { NextResponse } from 'next/server'
import { headers } from 'next/headers'
export async function POST(request: Request) {
const body = await request.text()
const headersList = await headers() // ✅ Await headers in Next.js 16
const signature = headersList.get('stripe-signature')
// Verify webhook signature
const event = stripe.webhooks.constructEvent(
body,
signature!,
process.env.STRIPE_WEBHOOK_SECRET!
)
// Handle event
switch (event.type) {
case 'payment_intent.succeeded':
await handlePaymentSuccess(event.data.object)
break
case 'payment_intent.failed':
await handlePaymentFailure(event.data.object)
break
}
return NextResponse.json({ received: true })
}
```
**See Template**: `templates/route-handler-api.ts`
**See Reference**: `references/route-handlers-reference.md`
---
## Proxy vs Middleware
**Next.js 16 introduces `proxy.ts`** to replace `middleware.ts`.
### Why the Change?
- **`middleware.ts`**: Runs on Edge runtime (limited Node.js APIs)
- **`proxy.ts`**: Runs on Node.js runtime (full Node.js APIs)
The new `proxy.ts` makes the network boundary explicit and provides more flexibility.
### Migration
**Before (middleware.ts)**:
```typescript
// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
// Check auth
const token = request.cookies.get('token')
if (!token) {
return NextResponse.redirect(new URL('/login', request.url))
}
return NextResponse.next()
}
export const config = {
matcher: '/dashboard/:path*',
}
```
**After (proxy.ts)**:
```typescript
// proxy.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function proxy(request: NextRequest) {
// Check auth
const token = request.cookies.get('token')
if (!token) {
return NextResponse.redirect(new URL('/login', request.url))
}
return NextResponse.next()
}
export const config = {
matcher: '/dashboard/:path*',
}
```
**See Template**: `templates/proxy-migration.ts`
**See Reference**: `references/proxy-vs-middleware.md`
---
## Parallel Routes & Route Groups
### 1. Parallel Routes
**Use Case**: Render multiple pages in the same layout (e.g., modal + main content, dashboard panels).
**Structure**:
```
app/
├── @modal/
│ ├── login/
│ │ └── page.tsx
│ └── default.tsx ← REQUIRED in Next.js 16
├── @feed/
│ ├── trending/
│ │ └── page.tsx
│ └── default.tsx ← REQUIRED in Next.js 16
└── layout.tsx
```
**Layout**:
```typescript
// app/layout.tsx
export default function Layout({
children,
modal,
feed,
}: {
children: React.ReactNode
modal: React.ReactNode
feed: React.ReactNode
}) {
return (
{modal}
{children}
)
}
```
**Default Files (REQUIRED)**:
```typescript
// app/@modal/default.tsx
export default function ModalDefault() {
return null
}
// app/@feed/default.tsx
export default function FeedDefault() {
return