Next.js App Router Patterns
Master Next.js App Router: Server Components vs Client Components, server actions, streaming with Suspense, parallel routes, and deployment patterns.
Next.js App Router (v13+) shifts the default to React Server Components - components that render on the server with direct data access, zero client JS. Understanding the Server/Client Component boundary, server actions, and streaming is essential for building performant Next.js apps.
Default to Server Components - they're faster and simpler. Add `"use client"` only when you need interactivity (event handlers, browser APIs, state). Use server actions for form mutations. Use Suspense for streaming granular data. Co-locate data fetching with the component that needs it.
Workflow
- Use Server Components for data fetchingServer Components fetch data on the server, reducing client bundle size and eliminating useEffect/loading states.
- Know when to add "use client"Client Components increase bundle size - add them only for interactivity.
- Use server actions for mutationsServer actions run on the server on form submit - no API route needed, no client-side fetch.
- Stream with Suspense boundariesSuspense allows shipping fast HTML immediately and streaming slow data later - no blocking.
- Implement Incremental Static RegenerationISR caches page renders, serving pre-built HTML while revalidating in the background.
Output Format
Server Components for data fetching, `"use client"` only for interactivity, server actions for forms, Suspense boundaries for streaming, `revalidatePath`/`revalidateTag` for ISR invalidation.
Examples
Server action with state: `"use server"; export async function signUp(prevState: ActionState, formData: FormData): Promise<ActionState> { const email = formData.get("email") as string; if (!isValidEmail(email)) return { error: "Invalid email" }; try { await createUser({ email }); redirect("/onboarding") } catch (e) { return { error: "Email already exists" } } }`Parallel data fetching: `export default async function ProductPage({ params }: { params: { id: string } }) { const [product, reviews, related] = await Promise.all([getProduct(params.id), getReviews(params.id), getRelated(params.id)]); return <Product product={product} reviews={reviews} related={related} /> }`ISR with tags: `async function getProduct(id: string) { return fetch(`/api/products/${id}`, { next: { tags: [`product-${id}`], revalidate: 3600 } }).then(r => r.json()) }`Edge Cases & Gotchas
- Context providers: React Context doesn't work in Server Components (no client runtime). Wrap context providers in a `"use client"` component, render them at the app layout level, and use context in Client Components only
- Cookies and headers in server actions: use `cookies()` and `headers()` from `next/headers` inside server actions to access request cookies/headers
- Metadata: export `metadata` (static) or `generateMetadata` (dynamic) from page/layout files for SEO - Next.js automatically injects into `<head>`
When to Break These Rules
- For heavily interactive SPA-like apps (rich dashboards, editors, games), the RSC model adds complexity without benefit. Use Next.js in pages router mode or a pure Vite SPA instead.
Reference Files
Next.js App Router: https://nextjs.org/docs/appServer Components: https://nextjs.org/docs/app/building-your-application/rendering/server-componentsServer Actions: https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutationsPatterns: https://nextjs.org/docs/app/building-your-application/rendering/composition-patterns