# React Server Components: Das neue Mental Model für 2026 **Meta-Description:** Umfassendes Verständnis von React Server Components. Composition Patterns, Data Fetching, Client/Server Boundary und Migration von Client-only Apps. **Keywords:** React Server Components, RSC, Server Components, React 19, Next.js, Client Components, Hybrid Rendering --- ## Einführung React Server Components (RSC) sind nicht nur ein Feature – sie sind ein **neues Mental Model**. Server Components rendern auf dem Server, senden **kein JavaScript** zum Client und können direkt auf Backend-Ressourcen zugreifen. --- ## Das Mental Model ``` ┌─────────────────────────────────────────────────────────────┐ │ REACT SERVER COMPONENTS MODEL │ ├─────────────────────────────────────────────────────────────┤ │ │ │ SERVER CLIENT │ │ ────────────────────── ────────────────────── │ │ ┌─────────────────────┐ │ │ │ Server Component │ ┌─────────────────────┐ │ │ │ ├── DB Access │ →→→ │ HTML + RSC Payload │ │ │ │ ├── File System │ │ (No JS Bundle!) │ │ │ │ ├── API Calls │ └─────────────────────┘ │ │ │ └── Heavy Compute │ │ │ └─────────────────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────────┐ ┌─────────────────────┐ │ │ │ Client Component │ →→→ │ JS Bundle │ │ │ │ ├── useState │ │ (Minimal!) │ │ │ │ ├── useEffect │ └─────────────────────┘ │ │ │ ├── Event Handlers │ │ │ │ └── Browser APIs │ │ │ └─────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────┘ ``` --- ## Server Components vs. Client Components | Aspekt | Server Component | Client Component | |--------|------------------|------------------| | **Rendering** | Server | Server + Client | | **JavaScript** | 0 bytes zum Client | Wird gebundelt | | **State** | Nein (kein useState) | Ja | | **Effects** | Nein (kein useEffect) | Ja | | **Event Handlers** | Nein | Ja | | **Browser APIs** | Nein | Ja | | **DB/Filesystem** | Ja (direkt) | Nein | | **async/await** | Ja (top-level) | Nein | --- ## Composition Patterns ### Pattern 1: Server Component als Wrapper ```tsx // app/products/page.tsx (Server Component) import { ProductGrid } from '@/components/ProductGrid'; import { FilterPanel } from '@/components/FilterPanel'; async function ProductsPage() { // Server-side data fetching const products = await prisma.product.findMany({ include: { category: true } }); return (
{/* Client Component für Interaktivität */} p.category)} /> {/* Server Component rendert Produkte */}
); } ``` ### Pattern 2: Client Component Children ```tsx // components/Modal.tsx (Client Component) 'use client'; import { useState } from 'react'; export function Modal({ children }: { children: React.ReactNode }) { const [isOpen, setIsOpen] = useState(false); return ( <> {isOpen && (
{/* Children können Server Components sein! */} {children}
)} ); } // Verwendung (Server Component) async function ProductPage({ params }) { const product = await fetchProduct(params.id); return (
{/* Server Component als Child von Client Component */}
); } ``` ### Pattern 3: Props Serialization ```tsx // ❌ Fehler: Funktionen können nicht serialisiert werden async function ProductCard({ product }) { return (
{product.name} {/* Fehler: onClick kann nicht zum Client */}
); } // ✅ Richtig: Interaktivität in Client Component async function ProductCard({ product }) { return (
{product.name} {/* Client Component für Click Handler */}
); } // components/ProductActions.tsx 'use client'; export function ProductActions({ productId }: { productId: string }) { return ( ); } ``` --- ## Data Fetching Patterns ### Pattern 1: Parallel Data Fetching ```tsx // ❌ Sequential (langsam) async function Dashboard() { const user = await fetchUser(); const stats = await fetchStats(); // Wartet auf user const notifications = await fetchNotifications(); // Wartet auf stats return ; } // ✅ Parallel (schnell) async function Dashboard() { const [user, stats, notifications] = await Promise.all([ fetchUser(), fetchStats(), fetchNotifications() ]); return ; } ``` ### Pattern 2: Data Colocation ```tsx // Jede Component holt ihre eigenen Daten // (Next.js dedupliziert automatisch gleiche Requests) async function UserProfile() { const user = await fetchUser(); // Request 1 return ; } async function UserPosts() { const user = await fetchUser(); // Dedupliziert! const posts = await fetchPosts(user.id); return ; } async function UserPage() { return (
); } ``` ### Pattern 3: Streaming mit Suspense ```tsx import { Suspense } from 'react'; async function ProductPage({ params }) { return (
{/* Sofort gerendert */} {/* Streamt sobald ready */} }> {/* Streamt unabhängig */} }>
); } async function ProductDetails({ id }) { // Diese Funktion blockiert nicht die ganze Page await new Promise(r => setTimeout(r, 1000)); // Simuliert DB-Call const details = await fetchProductDetails(id); return ; } ``` --- ## State Management mit Server Components ### Lifting State Up to URL ```tsx // Server Component liest State aus URL async function ProductsPage({ searchParams }: { searchParams: { sort?: string; filter?: string } }) { const products = await prisma.product.findMany({ orderBy: searchParams.sort ? { [searchParams.sort]: 'asc' } : undefined, where: searchParams.filter ? { category: searchParams.filter } : undefined }); return (
); } // Client Component für URL-Updates 'use client'; import { useRouter, useSearchParams } from 'next/navigation'; function FilterBar({ currentSort, currentFilter }) { const router = useRouter(); const searchParams = useSearchParams(); const updateFilter = (key: string, value: string) => { const params = new URLSearchParams(searchParams); params.set(key, value); router.push(`?${params.toString()}`); }; return (
); } ``` ### Server Actions für Mutations ```tsx // Server Action 'use server'; import { revalidatePath } from 'next/cache'; async function addToFavorites(productId: string) { await prisma.favorite.create({ data: { productId, userId: getCurrentUserId() } }); revalidatePath('/favorites'); } // Client Component nutzt Server Action 'use client'; import { useTransition } from 'react'; function FavoriteButton({ productId }) { const [isPending, startTransition] = useTransition(); return ( ); } ``` --- ## Migration Guide: Client → Server ### Schritt 1: Identifiziere reine Daten-Components ```tsx // Vorher: Client Component 'use client'; import { useEffect, useState } from 'react'; function ProductList() { const [products, setProducts] = useState([]); useEffect(() => { fetch('/api/products').then(r => r.json()).then(setProducts); }, []); return
{products.map(p => )}
; } // Nachher: Server Component async function ProductList() { const products = await prisma.product.findMany(); return
{products.map(p => )}
; } ``` ### Schritt 2: Extrahiere interaktive Teile ```tsx // Vorher: Alles Client 'use client'; function ProductCard({ product }) { const [count, setCount] = useState(1); return (

{product.name}

{product.description}

{product.price} €

setCount(Number(e.target.value))} />
); } // Nachher: Server + Client // ProductCard.tsx (Server) function ProductCard({ product }) { return (

{product.name}

{product.description}

{product.price} €

{/* Nur interaktiver Teil als Client */}
); } // AddToCartForm.tsx (Client) 'use client'; function AddToCartForm({ productId }) { const [count, setCount] = useState(1); return ( <> setCount(Number(e.target.value))} /> ); } ``` --- ## Performance-Vorteile ``` ┌─────────────────────────────────────────────────────────────┐ │ BUNDLE SIZE COMPARISON │ ├─────────────────────────────────────────────────────────────┤ │ │ │ Client-Only App (alles 'use client'): │ │ ├── React: 40KB │ │ ├── Components: 150KB │ │ ├── Dependencies: 200KB │ │ └── Total: ~390KB JS │ │ │ │ RSC-First App (Server wo möglich): │ │ ├── React: 40KB │ │ ├── Client Components: 30KB │ │ ├── Dependencies (Client only): 50KB │ │ └── Total: ~120KB JS │ │ │ │ Reduktion: 70%! │ │ │ └─────────────────────────────────────────────────────────────┘ ``` --- ## Fazit React Server Components transformieren wie wir React-Apps bauen: 1. **Server-First**: Default auf Server, Client nur wo nötig 2. **Zero Bundle**: Server Components senden kein JS 3. **Direct Access**: DB, Filesystem direkt nutzbar 4. **Composition**: Server + Client Components kombinierbar Das neue Mental Model: "Kann das auf dem Server bleiben?" --- ## Bildprompts 1. "Two puzzle pieces fitting together - server and client components, React logo in center" 2. "Data flowing from database through server component to client, simplified architecture" 3. "Bundle size comparison chart - before and after Server Components, dramatic reduction" --- ## Quellen - [React Server Components RFC](https://github.com/reactjs/rfcs/blob/main/text/0188-server-components.md) - [Next.js Server Components Docs](https://nextjs.org/docs/app/getting-started/server-and-client-components) - [Vercel: Understanding React Server Components](https://vercel.com/blog/understanding-react-server-components) - [Dan Abramov: RSC from Scratch](https://github.com/reactwg/server-components/discussions/5)