der komplette konzeptuelle Guide
React Server Components sind kein neues Rendering-Modell – sie sind ein fundamentaler Paradigmenwechsel. Das Denken in Client-only-Komponenten funktioniert nicht mehr. Wer RSC wirklich versteht, baut Apps, die weniger JavaScript ausliefern, schneller laden und Datenbankzugriffe direkt in Komponenten kapseln können.
Inhaltsverzeichnis
- 1. Das neue Paradigma: warum RSC existieren
- 2. RSC-Architektur: Server, Client und die Grenze dazwischen
- 3. Server Components: was sie können und was nicht
- 4. Client Components: use client und seine Bedeutung
- 5. Compositing-Muster: Server und Client korrekt verschachteln
- 6. Streaming mit Suspense: progressive Renderauslieferung
- 7. Datenzugriff direkt in Server Components
- 8. Server Actions: Formulare und Mutationen
- 9. RSC vs. klassisches SSR im Vergleich
- 10. Zusammenfassung
- 11. FAQ
1. Das neue Paradigma: warum RSC existieren
Das Grundproblem, das React Server Components lösen, lässt sich in einem Satz beschreiben: Zu viel JavaScript wird an den Client ausgeliefert, der es gar nicht braucht. Eine Komponente, die Datenbankdaten rendert und keine Interaktivität hat, muss nicht im Browser ausgeführt werden. Klassisches SSR rendert die Seite auf dem Server zu HTML, schickt aber das vollständige JavaScript-Bundle mit, damit React im Browser „hydratisieren" kann – die gesamte Komponentenlogik landet im Client-Bundle, obwohl viele Komponenten nie im Browser interaktiv werden.
RSC gehen einen anderen Weg: Server Components werden ausschließlich auf dem Server ausgeführt. Ihr Code kommt nie im JavaScript-Bundle an, das an den Browser gesendet wird. Bibliotheken, die nur Server Components nutzen, kosten keinen Bundle-Platz. Datenbankzugriffe, Dateisystem-Operationen und API-Aufrufe können direkt in Komponenten stattfinden – ohne useEffect, ohne useState, ohne Ladezustände für den initialen Datenabruf. Das reduziert die Komplexität erheblich und verbessert die Performance durch kleinere Bundles und schnellere Initial-Render-Zeiten.
2. RSC-Architektur: Server, Client und die Grenze dazwischen
In einer RSC-Architektur gibt es zwei Welten: die Serverwelt und die Clientwelt. Die Grenze zwischen ihnen ist explizit markiert und einseitig durchlässig. Alle Komponenten sind standardmäßig Server Components – sie werden auf dem Server ausgeführt, rendern zu einer serialisierten Komponentenbeschreibung (nicht zu HTML) und werden an den Client übertragen. Client Components müssen mit der Direktive 'use client' explizit markiert werden.
Die Grenze ist einseitig: Server Components können Client Components importieren und als Kinder rendern. Aber Client Components können keine Server Components importieren – das würde bedeuten, Server-only-Code ins Client-Bundle zu ziehen. Es gibt jedoch einen Ausweg: Server Components können als children-Prop an Client Components übergeben werden. Das Client-Component-Element erhält dann die bereits gerenderte RSC-Ausgabe als Prop, ohne den Server-Code selbst zu importieren. Dieses Compositing-Muster ist das Herzstück der RSC-Architektur.
// app/page.tsx — Server Component (default, no directive needed)
// Direct database access — no useEffect, no loading state needed
import { db } from '@/lib/db';
import { ProductCard } from './ProductCard'; // Client Component
import { Suspense } from 'react';
import { ProductSkeleton } from './ProductSkeleton';
// Server Component: runs only on server, never in browser bundle
export default async function ProductsPage() {
// Direct DB call — no API round-trip, no client bundle cost
const products = await db.product.findMany({
where: { active: true },
orderBy: { createdAt: 'desc' },
take: 20,
});
return (
<main>
<h1>Produkte</h1>
<Suspense fallback={<ProductSkeleton count={20} />}>
{/* Pass server-fetched data as props to client component */}
<ProductGrid products={products} />
</Suspense>
</main>
);
}
// app/ProductGrid.tsx — Server Component renders Client Components
async function ProductGrid({ products }: { products: Product[] }) {
return (
<div className="grid grid-cols-3 gap-4">
{products.map((product) => (
// ProductCard is 'use client' — handles add-to-cart interaction
<ProductCard key={product.id} product={product} />
))}
</div>
);
}
3. Server Components: was sie können und was nicht
Server Components können alles, was auf dem Server möglich ist: Datenbankzugriffe mit ORMs, Dateisystem-Lesen, Backend-APIs direkt aufrufen, Secrets aus Umgebungsvariablen lesen. Sie können asynchron sein – async function ServerComponent() ist ein vollständig unterstütztes Muster. Das bedeutet, Daten können mit await direkt im Component-Body abgerufen werden, ohne useEffect oder useState für den Ladezustand.
Was Server Components nicht können: Sie dürfen keinen Browser-spezifischen State halten (useState, useReducer), keine Effects registrieren (useEffect), keine Event-Handler verwenden (onClick, onChange) und keine Browser-APIs wie window, localStorage oder document nutzen. Der Versuch, diese in einer Server Component zu verwenden, führt zu einem Build-Fehler. Das ist beabsichtigt: Server Components sind pure Render-Maschinen ohne Seiteneffekte auf der Client-Seite.
4. Client Components: use client und seine Bedeutung
'use client' ist keine Aussage über wo eine Komponente ausgeführt wird – es ist eine Grenzmarkierung. Eine Client Component wird sowohl auf dem Server (für initiales SSR-HTML) als auch im Browser ausgeführt. Die Direktive sagt React: „Ab hier beginnt der Client-Graph". Alle Importe einer 'use client'-Datei werden automatisch in den Client-Bundle aufgenommen, auch wenn sie keine eigene 'use client'-Direktive haben.
Das ist eine kritische Konsequenz: Wenn eine Client Component eine große Bibliothek importiert, landet diese vollständig im Bundle. In Server Components würde dieselbe Bibliothek keinen Bundle-Platz kosten. Daher ist die strategische Frage: Welche Teile der App brauchen wirklich Interaktivität? Alles andere sollte Server Component bleiben. Eine gute Faustregel: Die 'use client'-Grenze so tief wie möglich im Komponentenbaum setzen – so bleibt der Großteil der App im Server-Graph und liefert minimales JavaScript.
// Compositing pattern: Server passes pre-rendered RSC as children to Client
// This avoids importing server code into client bundle
// app/Dashboard.tsx — Server Component (no directive)
import { SidebarNav } from './SidebarNav'; // Client Component (needs state)
import { Analytics } from './Analytics'; // Server Component (DB access)
export default async function Dashboard() {
const stats = await fetchStats(); // Direct server-side call
// Pass Server Component as children to Client wrapper
return (
<SidebarNav>
{/* Analytics is a Server Component passed as prop — not imported by client */}
<Analytics stats={stats} />
</SidebarNav>
);
}
// app/SidebarNav.tsx — Client Component wrapper
'use client';
import { useState } from 'react';
export function SidebarNav({ children }: { children: React.ReactNode }) {
const [collapsed, setCollapsed] = useState(false);
return (
<div className={`layout ${collapsed ? 'sidebar-collapsed' : ''}`}>
<nav>
<button onClick={() => setCollapsed(c => !c)}>Toggle</button>
{/* Nav items */}
</nav>
<main>{children}</main>
{/* children is pre-rendered RSC output — no server code in bundle */}
</div>
);
}
5. Compositing-Muster: Server und Client korrekt verschachteln
Das Compositing-Muster ist die wichtigste Technik für saubere RSC-Architektur. Das grundlegende Muster: Interaktive Wrapper-Komponenten sind Client Components, ihr Inhalt kann Server Components sein, die als children-Prop übergeben werden. Das ermöglicht, dass ein interaktiver Accordion-Wrapper im Client läuft, während sein gesamter Inhalt – Texte, Bilder, Produktdaten – als Server Component gerendert wird und kein Bundle-Gewicht hat.
Ein weiteres wichtiges Compositing-Muster ist das Leaf-Component-Muster: Interaktivität an die Blätter des Komponentenbaums verschieben. Statt eine gesamte Produktseite zur Client Component zu machen, bleibt die Produktseite Server Component. Nur der „In den Warenkorb"-Button ist eine kleine, eigenständige Client Component. Das minimiert den Client-Bundle-Anteil drastisch. Kontext-Provider sind typischerweise Client Components, die ganz oben im Baum stehen, um State für Kind-Client-Components bereitzustellen – können aber Server-Component-Children rendern.
6. Streaming mit Suspense: progressive Renderauslieferung
Streaming in RSC bedeutet, dass der Server nicht auf die langsamste Datenquelle wartet, bevor er mit dem Senden beginnt. Stattdessen sendet er den HTML-Rahmen sofort und streamt Teile der Seite nach, sobald sie fertig sind. Der Browser kann die Seite progressiv aufbauen – der Nutzer sieht sofort eine Shell, und Inhalte erscheinen, sobald ihre Daten verfügbar sind. Das verbessert die wahrgenommene Ladezeit erheblich, auch wenn die Gesamtladezeit gleich bleibt.
Das technische Vehikel dafür ist Suspense. Ein Server Component, der auf Daten wartet, kann in <Suspense fallback={...}> eingehüllt werden. React streamt sofort den Fallback-Inhalt (Skeleton, Spinner) und ersetzt ihn durch den echten Inhalt, sobald der Server Component fertig ist. Mehrere parallele Suspense-Boundaries ermöglichen unabhängiges Streaming verschiedener Seitenbereiche – die Sidebar kann laden während der Header bereits sichtbar ist und der Produktbereich noch wartet.
7. Datenzugriff direkt in Server Components
Einer der transformativsten Aspekte von RSC ist der direkte Datenbankzugriff in Komponenten. Was früher entweder eine API-Route erforderte (HTTP-Request vom Client) oder komplexes SSR-Datenfetching in getServerSideProps war, kann jetzt direkt im Komponenten-Body stehen. Ein ORM wie Prisma oder Drizzle kann direkt importiert werden, die Query wird serverseitig ausgeführt, das Ergebnis wird als Props an Kind-Komponenten weitergegeben.
Das hat tiefe Auswirkungen auf die Architektur: Datenfetching liegt näher an dem Ort, wo die Daten gebraucht werden. Statt Props-Drilling von einem Top-Level-Datenfetch durch viele Ebenen kann jede Server Component ihre eigenen Daten abrufen. React garantiert dabei, dass parallele Fetches gleichzeitig ausgeführt werden – der Promise.all-Ansatz ist bereits eingebaut. Für Caching und Deduplication zwischen mehreren Komponenten, die dieselben Daten brauchen, stellt Next.js die fetch-Memoization bereit.
8. Server Actions: Formulare und Mutationen
Server Actions sind die andere Seite der RSC-Medaille: Wenn Server Components das Lesen vereinfachen, vereinfachen Server Actions das Schreiben. Eine Server Action ist eine Funktion mit der Direktive 'use server', die im Client aufgerufen werden kann, aber auf dem Server ausgeführt wird. Sie kann direkt in ein <form action={...}> eingebunden werden und übernimmt das HTTP-Handling vollständig.
Das Progressive-Enhancement-Prinzip ist in Server Actions eingebaut: Ein Formular mit einer Server Action funktioniert auch ohne JavaScript im Browser, weil es als normales HTML-Formular mit POST-Request funktioniert. Mit JavaScript werden die Daten über einen fetch-ähnlichen Mechanismus übertragen, ohne Seitenreload. Server Actions können validieren, Datenbank-Operationen durchführen und den Next.js-Cache revalidieren – alles auf dem Server, ohne eine API-Route zu schreiben. Das eliminiert eine ganze Kategorie von Boilerplate-Code.
9. RSC vs. klassisches SSR im Vergleich
RSC und klassisches SSR lösen ähnliche Probleme auf fundamental unterschiedliche Weise. Die Unterschiede im Detail entscheiden darüber, welches Modell für welchen Use-Case besser passt.
| Aspekt | Klassisches SSR (Pages Router) | React Server Components (App Router) |
|---|---|---|
| Datenfetching | getServerSideProps auf Seitenebene | Direkt in jeder Komponente async/await |
| JavaScript-Bundle | Alle Komponenten im Bundle | Server Components kein Bundle-Anteil |
| Streaming | Nein (alles oder nichts) | Ja, mit Suspense-Boundaries |
| Formular-Handling | API-Route + fetch im Client | Server Actions direkt in Formularen |
| Lernkurve | Vertraut, eindeutiges Modell | Neue Konzepte: Grenze, Compositing |
Klassisches SSR bleibt valide für Projekte, die bereits auf dem Pages Router laufen und keine Migration rechtfertigen. RSC lohnt sich besonders für datenintensive Apps, bei denen Bundle-Größe und Time-to-First-Byte kritisch sind – E-Commerce, Content-Plattformen und Dashboards profitieren am stärksten. Der App Router ist nicht ein „besseres SSR", sondern ein anderes mentales Modell, das eine Lernphase erfordert.
Mironsoft
React Server Components · Next.js App Router · Migration · Architektur
Migration auf React Server Components?
Wir planen und implementieren die Migration vom Pages Router auf den App Router – mit korrekter Server/Client-Grenze, Compositing-Muster und Streaming-Architektur für maximale Performance.
RSC-Architektur
Server/Client-Grenze optimal setzen und Compositing-Muster implementieren
Streaming-Setup
Suspense-Boundaries und progressive Renderauslieferung für bessere UX
Migration
Pages Router zu App Router schrittweise migrieren ohne Betriebsunterbrechung
10. Zusammenfassung
React Server Components sind kein inkrementelles Update, sondern ein neues mentales Modell. Server Components laufen ausschließlich auf dem Server, haben Zugang zu Backend-Ressourcen und tragen kein JavaScript-Bundle-Gewicht. Client Components sind die interaktiven Teile, die im Browser laufen und mit 'use client' markiert werden. Die Server/Client-Grenze wird durch Compositing-Muster überbrückt: Server Components übergeben ihre gerenderte Ausgabe als children-Prop an Client-Wrapper.
Streaming mit Suspense liefert Seitenteile progressiv aus sobald ihre Daten bereit sind – ohne auf die langsamste Datenquelle zu warten. Server Actions vereinfachen Mutations und Formular-Handling, indem sie serverseitige Funktionen direkt im Client aufrufbar machen. Das Ergebnis: weniger JavaScript, schnellere Initial-Render-Zeiten, einfacheres Datenfetching und ein klareres Architektur-Modell für komplexe datenintensive React-Apps.
React Server Components — Das Wichtigste auf einen Blick
Server Components
Standardmäßig. Kein Bundle-Anteil, direkter DB-Zugriff, async/await im Body. Kein useState, useEffect oder Event-Handler.
Client Components
'use client' markiert die Grenze. Interaktivität, State, Effects. Grenze so tief wie möglich setzen für minimales Bundle.
Compositing
Server Components als children an Client-Wrapper übergeben. Client importiert nie Server-Code. Leaf-Components für Interaktivität.
Streaming
Suspense-Boundaries ermöglichen progressive Auslieferung. Skeleton sofort sichtbar, Inhalt streamt nach. Parallele Boundaries laufen unabhängig.