Startseite / Artikel / React Server Components: Die Architektur hinter dem rendern ohne Bundle.

React Server Components: Die Architektur hinter dem rendern ohne Bundle.

Verstehe die Gründe hinter React Server Components, von der Bundle-Größe und Problemen beim sequenziellen Ausführen bis zur Trennung zwischen Server und Client sowie den Payloads von RSC.

3487 Wörter

Falls Sie Zeit damit verbracht haben, sich mit React Server Components auseinanderzusetzen und am Ende noch verwirrter sind als zu Beginn, bedeutet das nicht, dass Ihnen etwas Offensichtliches entgangen ist. Es zeigt vielmehr, dass die Erklärungen, die Sie gefunden haben, zum Problem beigetragen haben. Einige Jahre lang wurden Server Components in der offiziellen Darstellung als „Komponenten, die auf dem Server gerendert werden“ beschrieben – ein Ausdruck, der verdächtig stark an das erinnert, was getServerSideProps oder das klassische Server-Rendering in Next.js bereits leisteten. Danach wurde behauptet, diese Komponenten „schicken keinen JavaScript-Code an den Client“, was eher wie ein Leistungstrick klingt als wie eine neue Art, Anwendungen zu strukturieren. Schließlich kam der App Router, und plötzlich galt jede Datei in einem Next.js-Projekt standardmäßig als Server Component, es sei denn, man fügte zu Beginn der Datei einen speziellen String ein.

Die Verwirrung war kein Versagen von Ihnen. Sie entstand, weil ein wirklich neues Paradigma mit Vokabular beschrieben wurde, das aus einem älteren übernommen wurde. Diese Anleitung zielt darauf ab, diese Lücke zu schließen. Wenn Sie mit dem Lesen fertig sind, sollten Sie nicht nur die Mechanismen von Server Components verstehen, sondern auch den Grund, warum sie eine völlig andere Denkweise bei React-Anwendungen ermöglichen.

Das Problem, das RSC tatsächlich löst

Bevor wir uns mit der Funktionsweise von Server Components beschäftigen, hilft es, zu verstehen, was React ursprünglich dazu veranlasst hat, sie zu entwickeln. Das Problem lag nie darin, dass das Server-Seitige Rendering zu langsam war. Das eigentliche Problem ist, dass Reacts Komponentenmodell auf der Annahme beruhte, alles würde im Browser ablaufen – selbst in Fällen, in denen diese Annahme unnötige Kosten verursachte.

Die Falle der Dateigröße

In the classic React model, every component you author turns into JavaScript that gets shipped to the browser, no exceptions. It makes no difference whether that component just renders some static markdown, does a simple date calculation, or hits a database. As long as it lives somewhere in your component tree, its code lives in your bundle too.

That creates an unforgiving trade-off. Adding more components inflates your JavaScript payload. A heavier payload pushes out your Time to Interactive. In practice, you end up delivering code to browsers that never had any reason to execute it.

The Waterfall Problem

Vor Server Components musste jede Komponente, die Daten benötigte, diese nach ihrer Ankunft beim Client abrufen. Das typische Muster bestand darin, eine Platzhalterstruktur anzuzeigen, einen useEffect-Aufruf zu starten, auf eine Antwort zu warten und erst danach den eigentlichen Inhalt anzuzeigen. Wenn dieser Inhalt eine verschachtelte Komponente mit eigenen Datenanforderungen enthielt, wiederholte sich derselbe Zyklus: ein weiterer Effect, eine weitere Wartezeit, eine weitere Verzögerung, die auf die vorherige aufgetürmt wurde. Diese Kettenreaktion ist das sogenannte Datenabruf-Wasserfall-Modell und erklärt, warum viele React-Anwendungen langsam wirken, selbst wenn ihre Backend-APIs schnell antworten.

Eine Lösung bestand darin, das Abrufen von Daten auf die Route-Ebene zu verlegen, indem man Tools wie getServerSideProps oder Route-Loader nutzte – doch das hatte einen Nachteil: Dadurch wurde die selbstständige Natur der Komponenten beeinträchtigt. Plötzlich musste die Seite über Daten Bescheid wissen, die konzeptionell zu ihren Kind-Komponenten gehörten, und die Komponenten waren nicht mehr unabhängige Einheiten.

Die Hydrationskosten

Beim traditionellen Server-Seiten-Rendering wird HTML auf dem Server erzeugt, dorthin gesendet und anschließend die gesamte Anwendung in JavaScript auf der Client-Seite neu gerendert, damit sie interaktiv wird. Das bedeutet, dass jede einzelne Komponente – auch jene, die niemals eine clientseitige Funktionalität benötigen – dennoch durch das Hydrationsverfahren gehen muss. Im Grunde zahlt man also eine Interaktivitätskosten selbst für völlig statische Inhalte.

React Server Components beheben all diese drei Probleme auf der Ebene der Architektur – und nicht als punktuelle Leistungsverbesserung.

Das Denkmuster: Server Components sind nicht „SSR 2.0“

Das ist die zentrale Idee, auf der dieser gesamte Leitfaden beruht: Server Components sind keine Methode zum Rendern von Seiten. Sie stellen vielmehr eine eigene Kategorie von Komponenten dar.

Klassisches React erkannte nur eine Art von Komponente an – solche, die im Browser ausgeführt wurden. Sie konnten Zustand speichern, Effekte ausführen und auf Ereignisse reagieren. Ihr gesamter Lebenszyklus, von der Erstellung bis zur Auflösung, fand auf der Client-Seite statt.

React Server Components fügen eine zweite Kategorie hinzu. Ein Server Component wird auf dem Server ausgeführt. Es kann frei auf das Dateisystem zugreifen, direkt eine Datenbank abfragen oder eine Bibliothek einbinden, die nur innerhalb von Node.js funktioniert. Was es jedoch nicht tun kann, ist useState, useEffect zu verwenden oder Ereignishandler anzuhängen, denn es läuft einfach nie im Browser. Es gibt keinen Hydratierungsprozess für solche Komponenten. Ihr Code wird auch nicht als JavaScript an den Client übertragen.

Eine nützliche Vorstellung dazu: Der React-Component-Tree ist nun komposit. Bestimmte Äste werden auf dem Server generiert, andere im Browser. Die serverseitigen Äste werden genau einmal während der Anfrage gerendert, und das Ergebnis wird in Form serialisierter React-Elemente an den Client gestreamt. Die clientseitigen Äste werden wie gewohnt im Browser gerendert und behalten alle interaktiven Funktionen bei, an die man gewöhnt ist.

Das ist ein anderer Mechanismus als SSR. Beim Server-side Rendering wird die gesamte Anwendung auf dem Server in HTML umgewandelt und anschließend im Browser wiederhergestellt. Im Gegensatz dazu rendernt RSC nur bestimmte Komponenten auf dem Server und überträgt ihren zugrundeliegenden Code niemals an den Browser.

Was tatsächlich an den Client gesendet wird

Eine Server-Komponente gibt beim Renderen kein HTML aus. Stattdessen erzeugt sie eine serialisierte Beschreibung ihrer Ausgabe, die als RSC Payload bezeichnet wird – ein Stream von JSON-ähnlichen Anweisungen, die React auf der Client-Seite mitteilen, wie es den Baum zusammenstellen soll, den es anzeigen muss.

Hier ist die Abfolge der Ereignisse im Hintergrund, wenn eine Seite mit Server-Komponenten angefordert wird:

  1. React rendernt die Server-Komponenten auf dem Server.
  • Für jede Server-Komponente gibt es die tatsächlich gerenderte Ausgabe aus – die erzeugten Elemente, nicht der Quellcode, der sie erstellt hat.
  • Für jede Client-Komponente wird hingegen eine Referenz ausgegeben: ein Marker, der anzeigt, wo diese Komponente montiert werden soll, zusammen mit den benötigten Props.
  • Der Server streamt diese gesamte Payload an den Browser.
  • Die React-Laufzeit des Browsers analysiert die Payload und erstellt den daraus resultierenden Baum.
  • Dann laden die Client-Komponenten ihren Code herunter und werden hydratisiert. Server-Komponenten, die bereits gerendert wurden, benötigen keinerlei Hydratierungsphase.
  • Der entscheidende Punkt ist, dass Code von Server-Komponenten niemals im JavaScript-Bundle enthalten ist, das an die Browser gesendet wird. Stellen Sie sich eine MarkdownRenderer-Komponente vor, die auf einer 200 KB großen Markdown-Parsing-Bibliothek basiert. Wenn es sich dabei um eine Server-Komponente handelt, bleibt die gesamte 200 KB große Bibliothek auf dem Server. Der Browser sieht nur die von der Parser-Bibliothek erzeugte Struktur, niemals die Bibliothek selbst.

    Das ist der Kern hinter der Behauptung einer Null-Bundle-Größe – und es handelt sich dabei nicht um eine einfache Optimierungstechnik. Es stellt tatsächlich eine grundlegende Veränderung dar, was das Erstellen einer React-Anwendung eigentlich beinhaltet.

    Die Grenze zwischen Server und Client

    In App Router zählt jedes Datei als Server Component, es sei denn, es wird ausdrücklich etwas anderes angegeben. Das ist nicht nur eine stilistische Standardeinstellung – es handelt sich um eine in das Framework eingebettete strukturelle Annahme, die darauf abzielt, dass der größte Teil Ihrer Benutzeroberfläche überhaupt nicht im Browser ausgeführt werden muss.

    Eine Client Component hingegen ist Code, der auf dem Rechner des Benutzers ausgeführt werden soll. Eine Datei wird so markiert, indem man an deren Anfang die Direktive "use client" platziert. Dies ist nicht nur empfehlender Text – sie fungiert als klare Trennlinie. Sie weist den Compiler von React an, dass alles, was in dieser Datei definiert ist, zusammen mit allen über Imports eingebundenen Elementen, verpackt und an den Browser gesendet werden muss.

    Die Regel, die dieses gesamte System kohärent hält, lautet wie folgt: Server-Komponenten können frei importiert und Client-Komponenten rendern, doch das Umgekehrte ist niemals zulässig – eine Client-Komponente kann keine Server-Komponente importieren.

    Auf den ersten Blick erscheint das rückwärtsgerichtet. Sollte Code auf Serverseite nicht von überall aus nutzbar sein, einschließlich Client-Dateien? Eigentlich nicht, wenn man verfolgt, wie Daten tatsächlich übertragen werden:

    • Zuerst wird eine Server-Komponente ausgeführt, die direkten Zugriff auf Ihre Datenbank und Backend-Ressourcen hat. Sie holt die benötigten Daten ab und erstellt eine Struktur. In dieser Struktur wird möglicherweise etwas wie <UserProfile /> renderiert, was Interaktivität erfordert – daher wird dieser Teil als Client-Komponente geschrieben. Die übergeordnete Server-Komponente gibt die abgerufenen Benutzerdaten als Eigenschaften weiter.
  • Die Client-Komponente nutzt einfach diese Props und rendernt die interaktiven Teile. Sie hat keine Möglichkeit, zurückzugehen und eine Server-Komponente einzubinden, denn zum Zeitpunkt des Laufs im Browser ist die Ausführung auf der Serverseite bereits längst abgeschlossen. Es gibt keinen Serverprozess mehr, den man aufrufen könnte.
  • Deshalb kann eine solche Anordnung nicht funktionieren:

    // ❌ Impossible: Client Component importing Server Component
    'use client';
    import { ServerDataFetcher } from './ServerDataFetcher'; // This breaks!
    export function ClientWidget() {
      return (
        <div>
          <ServerDataFetcher /> {/* Cannot render a server component here */}
        </div>
      );
    }
    

    Aber die Strukturierung in umgekehrter Weise ist völlig zulässig:

    // ✅ Correct: Server Component importing Client Component
    // This is a Server Component (no 'use client')
    import { ClientWidget } from './ClientWidget';
    async function ServerDataFetcher() {
      const data = await db.query('SELECT * FROM posts');
    
      return (
        <div>
          <h1>Latest Posts</h1>
          {data.map(post => (
            <ClientWidget key={post.id} post={post} />
          ))}
        </div>
      );
    }
    

    Das Muster ist einfach: Die Server-Komponente kümmert sich um das Abrufen der Daten und das strukturelle Rendering, leitet anschließend die Daten an interaktive Komponenten weiter, die sich an den „Blättern“ des Baums befinden. Diese Blattkomponenten sind für Klicks, Formulareingaben und Animationen zuständig. Die resultierende Architektur ist im Grunde ein auf dem Server renderter Baum, bei dem interaktive Elemente an seinen Rändern verteilt sind.

    Asynchrone Komponenten: Die Revolution beim Datenabrufen

    Klassisches React erlaubte es niemals, dass Komponentenfunktionen asynchron sind. Das Direktverwenden von await innerhalb des Komponentenkörpers war nicht möglich – man musste stattdessen useEffect verwenden und den Ladezustand manuell verwalten.

    Server Components durchbrechen diese Beschränkung: Sie können async sein. Es mag sich um eine geringfügige syntaktische Erweiterung handeln, doch sie verändert die zugrunde liegende Architektur erheblich.

    // ✅ Server Component: Direct data access, no useEffect
    async function BlogPostList() {
      // This runs on the server. No fetch call in the browser.
      const posts = await db.post.findMany({
        orderBy: { createdAt: 'desc' },
        take: 10
      });
    
      return (
        <ul>
          {posts.map(post => (
            <li key={post.id}>
              <h2>{post.title}</h2>
              <p>{post.excerpt}</p>
            </li>
          ))}
        </ul>
      );
    }
    

    Achten Sie auf alles, was hier fehlt. Es gibt weder einen useState-Zustand zur Überwachung eines Ladezeichens noch einen useEffect, der eine Abfrage auslöst, noch ein manuell erstelltes Skelett-Interface. Die Komponente wandelt einfach die Datenbankdaten direkt in React-Elemente um. Die Abfragen finden nun pro Komponente statt und nicht pro Route, außerdem vollständig auf der Serverseite – niemals im Browser.

    Deshalb verschwindet das Wasserfall-Problem – der Server kann alle asynchronen Komponenten gleichzeitig verarbeiten, bevor er eine Antwort an den Client sendet. Datenbankaufrufe werden im selben Rechenzentrum wie Ihr Backend ausgeführt, wodurch die Latenz in Mikrosekunden gemessen wird und nicht in Millisekunden, wie es bei Übertragungen über eine mobile Verbindung üblich ist.

    Die „use client“-Direktive: Wann und warum

    Durch das Hinzufügen von "use client" wird eine Datei als zum Browser-Runtime gehörend markiert. Sie ist erforderlich, sobald eine Komponente etwas berührt, das im Wesentlichen clientseitig ist:

    • Lokaler Zustand über useState oder useReducer
    • Lifecycle-Hooks wie useEffect oder useLayoutEffect
    • Event-Verarbeitung, beispielsweise onClick oder onSubmit
  • Direkte Browser-APIs – localStorage, window, document
  • Hooks, die mit dem Browserkontext verbunden sind, einschließlich bestimmter Verwendungen von useRouter oder Funktionen wie useMediaQuery
  • Der häufige Fehler besteht darin, diese Direktive zu hoch im Komponentenbaum anzubringen. Entwickler wandeln oft eine ganze Seite in ein Client-Komponente um, nur weil ein eingebetteter Button Interaktivität benötigt.

    // ❌ Bad: Making the whole page client-side for one interactive element
    'use client';
    import { useState } from 'react';
    import { HeroSection } from './HeroSection'; // Static, could be server
    import { LikeButton } from './LikeButton';   // Interactive, needs client
    export default function Page() {
      return (
        <div>
          <HeroSection />
          <LikeButton />
        </div>
      );
    }
    

    In diesem Auszug ist HeroSection reine statische Markup-Struktur, die problemlos weiterhin auf der Serverseite gerendert werden könnte. Da jedoch "use client" auf Ebene des Elternteils deklariert wurde, wird jede darunter stehende Importierung – einschließlich dieser statischen Section – dennoch gebündelt und an den Browser gesendet.

    Die Lösung: use client" weiter nach unten im Baum platzieren

    Die Lösung besteht darin, die Seite selbst als Serverkomponente zu belassen und den interaktiven Teil als Blattknoten zu behandeln, der in sie importiert wird.

    // ✅ Good: Page is server, only LikeButton is client
    // page.tsx (Server Component by default)
    import { HeroSection } from './HeroSection';
    import { LikeButton } from './LikeButton';
    export default function Page() {
      return (
        <div>
          <HeroSection />
          <LikeButton postId="123" />
        </div>
      );
    }
    
    // LikeButton.tsx
    'use client';
    import { useState } from 'react';
    export function LikeButton({ postId }) {
      const [liked, setLiked] = useState(false);
    
      return (
        <button onClick={() => setLiked(!liked)}>
          {liked ? '❤️' : '🤍'}
        </button>
      );
    }
    

    Mit dieser Struktur wird weder HeroSection noch Page jemals als JavaScript an den Browser gesendet. Nur LikeButton zusammen mit seiner useState-Aufruf wird übertragen. Dies ist das praktische Prinzip hinter dem Ziel einer Null-Paketgröße: So viel wie möglich des Komponentenbaums bleibt auf dem Server, während die Client-Seite nur für diejenigen Knoten genutzt wird, die tatsächlich Interaktivität benötigen.

    Vermischung: Das Muster, das RSC mächtig macht

    Die Technik, die RSC seine wahre Stärke verleiht, ist das Verschachteln – Server-Komponenten werden in Client-Komponenten eingebettet, serverseitig erzeugte Daten werden als Props übergeben, und diese Client-Komponenten ermöglichen es, children-Slots bereitzustellen, die weitere Server-Komponenten aufnehmen können.

    // Layout.tsx (Server Component)
    import { Sidebar } from './Sidebar';
    import { AnalyticsProvider } from './AnalyticsProvider';
    export default async function DashboardLayout({ children }) {
      // Fetch user data on the server
      const user = await getCurrentUser();
      const permissions = await getUserPermissions(user.id);
    
      return (
        <div className="dashboard">
          <Sidebar user={user} permissions={permissions} />
    
          {/* AnalyticsProvider is a Client Component */}
          <AnalyticsProvider userId={user.id}>
            {/* children here can be a Server Component page */}
            <main>{children}</main>
          </AnalyticsProvider>
        </div>
      );
    }
    
    // AnalyticsProvider.tsx
    'use client';
    import { createContext, useContext } from 'react';
    const AnalyticsContext = createContext(null);
    export function AnalyticsProvider({ userId, children }) {
      // Client-side analytics initialization
      useEffect(() => {
        analytics.identify(userId);
      }, [userId]);
    
      return (
        <AnalyticsContext.Provider value={{ userId }}>
          {children}
        </AnalyticsContext.Provider>
      );
    }
    

    Hier läuft DashboardLayout auf dem Server und ruft direkt die Datenbank ab. Es leitet diese Daten an Sidebar weiter, welches je nach seinen Anforderungen entweder eine Server- oder Client-Komponente sein kann. Zudem umhüllt es den Rest der Seite mit AnalyticsProvider, einer Client-Komponente, die im Browser erforderlich ist, um eine Bibliothek für Drittanbieter-Analytik zu initialisieren.

    Der wichtige Aspekt ist die children-Eigenschaft. Alles, was innerhalb von AnalyticsProvider dargestellt wird, muss nicht automatisch zu Client-Code werden, nur weil es dort eingebettet ist. React leitet die bereits gerenderte Ausgabe des Server Components über den Client Component als children weiter – der Client Component fungiert lediglich als Wrapper oder Grenze, nicht als Konverter, der alles darin zwingend auf der Client-Seite ausführt.

    Genau so werden hybride Benutzeroberflächen erstellt: serverseitig gerenderte Inhalte werden in Wrapper eingebettet, die ausschließlich auf der Client-Seite laufen, genau dort, wo Interaktivität tatsächlich erforderlich ist.

    Der RSC Payload: Ein Blick hinter die Kulissen

    Die Renderung von Server Components erzeugt keinen HTML-Code direkt. Stattdessen sendet React den RSC Payload, einen binären Stream, der konzeptionell wie folgt aussieht:

    1:I["node_modules/react/jsx-runtime.js", "jsx"]
    2:I["./components/ClientWidget.js", "default"]
    0:["quot;, "div", null, {"children": [
      ["quot;, "h1", null, {"children": "Latest Posts"}],
      ["quot;, "@2", null, {"postId": "123", "title": "Hello World"}]
    ]}]
    

    Jede Zeile in diesem Stream ist eine Anweisung. Das Symbol $ kennzeichnet ein React-Element. I steht für die Importierung eines Client-Component-Moduls. Eine Referenz wie @2 verweist auf den zweiten Import – in diesem Fall auf ClientWidget. Beachten Sie, dass der Server bereits den h1-Tag vollständig gerendert hat, während für ClientWidget lediglich die Props weitergeleitet und auf den Ort seines Codes verwiesen wurde, ohne ihn selbst zu rendern.

    Sobald der Browser diesen Stream erhält, löst er diese Importverweise auf, lädt die notwendigen Client-Component-Bundles herunter und stellt den endgültigen Komponentenbaum zusammen. Da die Daten schrittweise übertragen werden, muss der Browser nicht auf den gesamten Inhalt warten, bevor er mit dem Rendern beginnt. Genau diese schrittweise Lieferung ermöglicht es RSC, Server-Rendering mit Streaming zu unterstützen, ohne auf die üblichen Verzögerungen durch das Hydratierungsverfahren angewiesen zu sein.

    Caching und Neuvalidierung

    Next.js 15 und neuer bieten Server Components Zugang zu einem umfassenden Speicherstrategien-Portfolio:

    1. Statisches Rendering (Standard). Sofern eine Komponente keine dynamischen Daten liest oder keinen nicht gespeicherten Abruf durchführt, wird sie zur Zeit der Kompilierung statisch gerendert.
    // Cached indefinitely at build time
    async function ProductList() {
      const products = await fetch('https://api.example.com/products');
      // ...
    }
    
    1. Dynamische Darstellung. Durch Aufruf von cookies(), headers() oder Lesen von searchParams sowie durch explizite Festlegung von export const dynamic = 'force-dynamic' wird die Komponente stattdessen bei jeder eingehenden Anfrage neu gerendert.
    2. Aufräumung nach Ablauf einer Zeitvorlage.
    // Revalidate every 60 seconds
    async function ProductList() {
      const products = await fetch('https://api.example.com/products', {
        next: { revalidate: 60 }
      });
      // ...
    }
    
    1. Aufräumung auf Anfrage.
    // app/api/revalidate/route.ts
    import { revalidatePath } from 'next/cache';
    export async function POST() {
      revalidatePath('/products');
      return Response.json({ revalidated: true });
    }
    

    Der wichtigste Punkt ist, dass sich das Caching-Verhalten nun an einzelne Komponenten statt an ganze Routen bindet. Zwei Komponenten auf derselben Seite können völlig unterschiedliche Caching-Regeln haben. Eine solche Feinabstimmung existiert im klassischen SSR nicht, wo ein einziger Cache-Header die gesamte Seite gleichzeitig steuert.

    Anhaltende Missverständnisse

    „Server Components existieren hauptsächlich für SEO.“ Nicht ganz – eine bessere Suchindizierung ist ein Nebeneffekt, nicht das Ziel. Die eigentliche Motivation besteht darin, Client-Side-JavaScript zu reduzieren und das Abrufen von Daten auf Komponentenebene zu ermöglichen.

    „Jede Komponente, die einen Hook verwendet, benötigt use client.“ Das gilt nur dann, wenn der Hook tatsächlich auf Browser-APIs angewiesen ist. Der use-Hook in React 19 kann Versprechen entpacken und den Kontext direkt innerhalb von Server Components lesen, sodass viele Hooks problemlos funktionieren, ohne jemals den Client zu berühren.

    „Server Components machen API-Routen überflüssig.“ Das ist nicht der Fall – beides arbeitet zusammen. Server Components übernehmen die Aufgabe, bei der ersten Darstellung Daten abzurufen, doch man benötigt weiterhin API-Routen, um Änderungen zu verarbeiten, eingehende Webhooks von Dritten sowie alle nach der Hydratierung auf Client-Seite stattfindenden Abrufvorgänge zu handhaben.

    „Server Components haben keinen Zustand.“ Ihnen fehlt ein browserbasiertes Zustandskonzept – es gibt kein useState – doch sie können frei auf serverseitige Zustandsquellen wie Datenbanken, Caches, das Dateisystem sowie Umgebungsvariablen zugreifen.

    „Context ist in Server Components nicht verfügbar.“ Man kann tatsächlich kein React Context innerhalb eines Server Components verwenden, da Context speziell dazu dient, das Übertragen von Eigenschaften in clientseitig rendernden Strukturen zu vermeiden. Stattdessen kann man Daten als gewöhnliche Eigenschaften weitergeben, und da die Serverrenderung den Komponentenbaum synchron von oben nach unten auflöst, bietet Next.js zusätzlich Optionen wie unstable_rootParams neben dem herkömmlichen Eigenschaftsübertragungsmechanismus.

    Die Situation im Jahr 2026

    Nun, da React 19 Stabilität erreicht hat, ist auch die zugehörige Tooling-Infrastruktur erheblich weiterentwickelt worden:

    • Durch Server Actions können Client Components eine asynchrone Funktion aufrufen, die direkt auf dem Server ausgeführt wird, wodurch die Grenze zwischen Client und Server bei Änderungen abgemildert wird.
    • Der use-Hook bietet Client Components eine Möglichkeit, Promises und Context zu entpacken, ohne auf useEffect zurückgreifen zu müssen.
    • Partial Prerendering (PPR) in Next.js 15 ermöglicht es, eine statische Struktur direkt über das CDN bereitzustellen, während dynamische Abschnitte vom Origin-Server gestreamt werden.
    • Der React Compiler kümmert sich automatisch um die Memoisierung von Client Components, reduziert manuelle useMemo-Aufrufe und sorgt dafür, dass der Übergang zwischen Server- und Client-Umgebung reibungsloser verläuft.

    Das Konzeptmodell hat sich an diesem Punkt stabilisiert. Server Components sind nicht mehr eine Funktion, für die man aktiv entscheiden muss – sie bilden vielmehr die Grundvoraussetzung dafür, wie moderne React-Anwendungen erstellt werden. Client Components sind nun die Ausnahme: der bewusst vorgesehene Ausweg für die interaktive Schicht.

    Eine Veränderung in der Architektur, nicht nur im Leistungsbereich

    React Server Components sind keineswegs nur eine Leistungsverbesserung. Sie stellen eine Neubewertung dessen dar, wo React-Code tatsächlich ausgeführt wird. Etwa zehn Jahre lang war React im Grunde eine Browser-Bibliothek, die hauptsächlich aus SEO-Gründen auf Serverseite laufen musste. Heute ist es etwas anderes: ein vollständiges Component-System, das an beiden Enden der Netzwerkverbindung arbeitet, wobei jedes Ende klare Grenzen, Verantwortlichkeiten und Leistungsprofile aufweist.

    Der Server ist nicht mehr nur ein Ort, an dem Daten abgerufen werden – er ist nun eine echte Renderumgebung. Der Browser wiederum ist nicht länger die einzige Plattform für React-Komponenten; vielmehr ist er genau der Ort, an dem Interaktivität entsteht.

    Sobald dieses Verständnis greift, verschwindet ein Großteil der Verwirrung bezüglich RSC. Die Frage wandelt sich von „Brauche ich hier use client?“ zu „Wohin gehört dieser spezifische Logikteil?“ Das ist die Frage, die gestellt werden sollte – und es ist die Denkweise, die mit wachsenden Anwendungen weiterhin relevant bleibt.

    Verwandte Artikel

  • Abkehr von Runtime CSS-in-JS: Server Components und Styles zur Zeit der Kompilierung — Warum Runtime CSS-in-JS mit React Server Components sowie Leistungszielen kollidiert, wie sich Tailwind, CSS Modules und Bibliotheken ohne Laufzeitverarbeitung vergleichen lassen, und wie man sicher migrieren kann.