Next.js App Router mit Claude Code: Server Components 2026

Der App Router ist die Zukunft von Next.js — React Server Components, Streaming, verschachtelte Layouts, Server Actions. Claude Code kennt alle App-Router-Patterns und generiert produktionsreife Next.js-Strukturen mit klarer Server/Client-Trennung.

Server Components vs. Client Components

ServerWas im Server bleibt, was zum Client geht

# Prompt: "Erkläre die Server/Client-Grenze im Next.js App Router mit Beispielen" // ✅ SERVER COMPONENT (Standard im App Router) // app/products/page.tsx — kein 'use client' nötig import { db } from '@/lib/db' export default async function ProductsPage() { // Direkt DB-Zugriff — kein API-Call nötig! const products = await db.product.findMany({ where: { active: true } }) return ( <div> {products.map(p => <ProductCard key={p.id} product={p} />)} </div> ) } // → Kein JS zum Client gesendet, kein Client-Side-Fetching, kein Loading-State // ✅ CLIENT COMPONENT — nur wenn nötig // components/AddToCart.tsx 'use client' // Directive MUSS erste Zeile sein import { useState } from 'react' export function AddToCart({ productId }: { productId: string }) { const [loading, setLoading] = useState(false) return <button onClick={() => addToCart(productId)}>In den Warenkorb</button> } // WANN Client Component? // ✅ useState, useEffect, Event-Handler // ✅ Browser-APIs (localStorage, window) // ✅ Interaktive Widgets (Dropdown, Modal, Form) // ❌ NICHT für Datenbankzugriffe, Secrets, schwere Logik
Claude Code Pattern-Prompt: "Analysiere diese Komponente: was gehört in Server Component, was braucht 'use client'? Trenne klar nach Server/Client-Grenze." Claude Code refaktoriert bestehende Pages-Router-Code präzise auf App Router.

Layouts und Verschachtelung

LayoutNested Layouts und Shared UI

# Prompt: "App Router Ordnerstruktur für SaaS mit Auth-Layout und Dashboard-Layout" app/ ├── layout.tsx # Root Layout (html, body, fonts) ├── page.tsx # Landing Page ├── (marketing)/ # Route Group — kein URL-Segment! │ ├── about/page.tsx │ └── blog/page.tsx ├── (auth)/ # Auth-Layout (kein Sidebar) │ ├── layout.tsx # Nur zentrierter Card │ ├── login/page.tsx │ └── register/page.tsx └── dashboard/ # Dashboard-Layout (mit Sidebar) ├── layout.tsx # Sidebar + Header ├── page.tsx # /dashboard ├── settings/ │ └── page.tsx # /dashboard/settings └── [teamId]/ # Dynamische Route └── page.tsx # /dashboard/[teamId] // app/dashboard/layout.tsx export default function DashboardLayout({ children }: { children: React.ReactNode }) { return ( <div className="flex h-screen"> <Sidebar /> {/* Server Component */} <main className="flex-1 overflow-y-auto"> <Header /> {children} {/* Aktive Page */} </main> </div> ) }

Loading, Error und Suspense

StatesLoading-UI und Error-Boundaries

# Prompt: "Loading und Error States für Dashboard-Page mit Streaming" // app/dashboard/loading.tsx — automatisch als Suspense-Fallback export default function DashboardLoading() { return <DashboardSkeleton /> // Zeigt während Daten laden } // app/dashboard/error.tsx — Client Component Pflicht! 'use client' export default function DashboardError({ error, reset }: { error: Error & { digest?: string } reset: () => void }) { return ( <div> <h2>Fehler: {error.message}</h2> <button onClick={reset}>Nochmal versuchen</button> </div> ) } // Granulares Streaming mit Suspense import { Suspense } from 'react' export default function Dashboard() { return ( <div> <h1>Dashboard</h1> <Suspense fallback={<StatsSkeleton />}> <StatsSection /> {/* Streamt unabhängig */} </Suspense> <Suspense fallback={<TableSkeleton />}> <RecentOrders /> {/* Streamt unabhängig */} </Suspense> </div> ) } // → Beide Sections laden parallel, erscheinen sobald bereit

Server Actions: Formulare ohne API-Routes

ActionsServer Actions für Mutations

# Prompt: "Server Action für Profil-Update mit Validierung und Revalidation" // app/dashboard/settings/actions.ts 'use server' import { revalidatePath } from 'next/cache' import { redirect } from 'next/navigation' import { auth } from '@/lib/auth' export async function updateProfile(formData: FormData) { const session = await auth() if (!session) redirect('/login') const name = formData.get('name') as string const validated = profileSchema.parse({ name }) await db.user.update({ where: { id: session.userId }, data: validated }) revalidatePath('/dashboard') // Cache invalidieren // return { success: true } — oder redirect } // app/dashboard/settings/page.tsx — Formular direkt mit Action export default function SettingsPage() { return ( <form action={updateProfile}> {/* Server Action direkt! */} <input name="name" /> <button type="submit">Speichern</button> </form> ) } // → Kein fetch(), kein API-Route, kein useState — direkt Server-Aufruf

Route Handlers und Metadata API

# Prompt: "Route Handler für Webhook-Empfang und dynamische OG-Images" // app/api/webhooks/stripe/route.ts import { NextRequest } from 'next/server' export async function POST(req: NextRequest) { const body = await req.text() const sig = req.headers.get('stripe-signature')! const event = stripe.webhooks.constructEvent(body, sig, process.env.STRIPE_WEBHOOK_SECRET!) switch (event.type) { case 'checkout.session.completed': await activateSubscription(event.data.object) break } return Response.json({ received: true }) } // Metadata API für SEO // app/products/[slug]/page.tsx export async function generateMetadata({ params }: Props) { const product = await getProduct(params.slug) return { title: product.name, description: product.description, openGraph: { title: product.name, images: [{ url: product.imageUrl }] } } } // generateStaticParams für SSG export async function generateStaticParams() { const products = await db.product.findMany({ select: { slug: true } }) return products.map(p => ({ slug: p.slug })) }

CachingData Fetching und Caching-Strategien

# Prompt: "Erkläre Next.js App Router Caching-Strategien für verschiedene Datentypen" // Statisch gecacht (Build-time) — Default für Server Components const config = await fetch('https://api.example.com/config') // → Gebaut einmal, dann gecacht bis Redeploy // Zeit-basierte Revalidierung const posts = await fetch('https://api.blog.com/posts', { next: { revalidate: 3600 } // 1 Stunde }) // Kein Cache — immer frisch (dynamic) const price = await fetch('https://api.stocks.com/price', { cache: 'no-store' }) // On-demand Revalidierung nach Mutation import { revalidateTag } from 'next/cache' const data = await fetch('https://api.example.com/posts', { next: { tags: ['posts'] } }) // Nach Update: revalidateTag('posts') // Alle 'posts'-gecachten Requests ungültig
App Router vs Pages Router: getServerSideProps, getStaticProps und getStaticPaths sind im App Router ersetzt durch async Server Components + fetch-Caching. Claude Code migriert Pages-Router-Code automatisch auf die neuen Patterns.

Next.js-Modul im Kurs

Im Claude Code Mastery Kurs: vollständiges Next.js App Router-Modul mit Server Components, Server Actions, Layouts, Caching-Strategien, Route Handlers und Migration vom Pages Router — für moderne Full-Stack-Apps.

14 Tage kostenlos testen →