Kompositionsmuster für Server- und Client-Komponenten
Beim Erstellen von React-Anwendungen musst du dir überlegen, welche Teile deiner Anwendung auf dem Server und welche auf dem Client gerendert werden sollen. Diese Seite behandelt empfohlene Kompositionsmuster für die Verwendung von Server- und Client-Komponenten.
Wann Server- und Client-Komponenten einsetzen?
Hier ist eine kurze Übersicht der verschiedenen Anwendungsfälle für Server- und Client-Komponenten:
Was möchtest du tun? | Server-Komponente | Client-Komponente |
---|---|---|
Daten abrufen | ||
Auf Backend-Ressourcen zugreifen (direkt) | ||
Sensible Informationen auf dem Server behalten (Zugriffstoken, API-Keys etc.) | ||
Große Abhängigkeiten auf dem Server behalten / Client-seitiges JavaScript reduzieren | ||
Interaktivität und Event Listener hinzufügen (onClick() , onChange() etc.) | ||
State und Lifecycle Effects verwenden (useState() , useReducer() , useEffect() etc.) | ||
Browser-only APIs verwenden | ||
Custom Hooks verwenden, die von State, Effects oder Browser-only APIs abhängen | ||
React-Klassenkomponenten verwenden |
Muster für Server-Komponenten
Bevor du dich für Client-seitiges Rendering entscheidest, möchtest du vielleicht bestimmte Aufgaben auf dem Server erledigen, wie das Abrufen von Daten oder den Zugriff auf deine Datenbank oder Backend-Services.
Hier sind einige gängige Muster für die Arbeit mit Server-Komponenten:
Daten zwischen Komponenten teilen
Beim Abrufen von Daten auf dem Server kann es Fälle geben, in denen du Daten über verschiedene Komponenten hinweg teilen musst. Zum Beispiel könnte ein Layout und eine Seite von denselben Daten abhängig sein.
Anstatt React Context zu verwenden (was auf dem Server nicht verfügbar ist) oder Daten als Props zu übergeben, kannst du fetch
oder die cache
-Funktion von React verwenden, um dieselben Daten in den Komponenten abzurufen, die sie benötigen. Dabei musst du dir keine Sorgen machen, dass dieselben Daten mehrfach angefragt werden. Der Grund dafür ist, dass React fetch
automatisch erweitert, um Datenanfragen zu memoizieren, und die cache
-Funktion verwendet werden kann, wenn fetch
nicht verfügbar ist.
Sieh dir ein Beispiel für dieses Muster an.
Server-only Code vom Client-Kontext fernhalten
Da JavaScript-Module zwischen Server- und Client-Komponenten geteilt werden können, ist es möglich, dass Code, der eigentlich nur für den Server gedacht war, sich in den Client einschleicht.
Betrachte zum Beispiel die folgende Datenabfrage-Funktion:
Auf den ersten Blick scheint getData
sowohl auf dem Server als auch auf dem Client zu funktionieren. Diese Funktion enthält jedoch einen API_KEY
, der mit der Absicht geschrieben wurde, dass er nur auf dem Server ausgeführt wird.
Da die Umgebungsvariable API_KEY
nicht mit NEXT_PUBLIC
beginnt, ist sie eine private Variable, auf die nur auf dem Server zugegriffen werden kann. Um zu verhindern, dass deine Umgebungsvariablen an den Client durchsickern, ersetzt Next.js private Umgebungsvariablen durch einen leeren String.
Dadurch funktioniert getData()
zwar importiert und ausgeführt werden kann, aber nicht wie erwartet. Und obwohl die Variable öffentlich machen würde, dass die Funktion auf dem Client funktioniert, möchtest du vielleicht keine sensiblen Informationen an den Client weitergeben.
Um diese Art von unbeabsichtigter Client-Nutzung von Server-Code zu verhindern, können wir das server-only
Paket verwenden. Dies gibt anderen Entwicklern einen Build-Zeit-Fehler, wenn sie versehentlich eines dieser Module in eine Client-Komponente importieren.
Um server-only
zu verwenden, installiere zunächst das Paket:
Dann importiere das Paket in jedes Modul, das Server-only Code enthält:
Jetzt erhält jede Client-Komponente, die getData()
importiert, einen Build-Zeit-Fehler mit der Erklärung, dass dieses Modul nur auf dem Server verwendet werden kann.
Das entsprechende Paket client-only
kann verwendet werden, um Module zu kennzeichnen, die Client-only Code enthalten – zum Beispiel Code, der auf das window
-Objekt zugreift.
Verwendung von Drittanbieter-Paketen und Providern
Da Server-Komponenten ein neues React-Feature sind, beginnen Drittanbieter-Pakete und Provider im Ökosystem gerade erst damit, die "use client"
-Direktive für Komponenten hinzuzufügen, die Client-only Features wie useState
, useEffect
und createContext
verwenden.
Heute haben viele Komponenten aus npm
-Paketen, die Client-only Features verwenden, noch nicht diese Direktive. Diese Drittanbieter-Komponenten funktionieren wie erwartet innerhalb von Client-Komponenten, da diese die "use client"
-Direktive haben, aber sie funktionieren nicht innerhalb von Server-Komponenten.
Nehmen wir zum Beispiel an, du hast das hypothetische acme-carousel
Paket installiert, das eine <Carousel />
-Komponente enthält. Diese Komponente verwendet useState
, hat aber noch nicht die "use client"
-Direktive.
Wenn du <Carousel />
innerhalb einer Client-Komponente verwendest, funktioniert sie wie erwartet:
Wenn du sie jedoch direkt in einer Server-Komponente verwendest, siehst du einen Fehler:
Der Grund dafür ist, dass Next.js nicht weiß, dass <Carousel />
Client-only Features verwendet.
Um dies zu beheben, kannst du Drittanbieter-Komponenten, die von Client-only Features abhängig sind, in deine eigenen Client-Komponenten einpacken:
Jetzt kannst du <Carousel />
direkt in einer Server-Komponente verwenden:
Du musst voraussichtlich nicht viele Drittanbieter-Komponenten einpacken, da du sie wahrscheinlich innerhalb von Client-Komponenten verwendest. Eine Ausnahme sind Provider, da sie auf React State und Context basieren und typischerweise am Root einer Anwendung benötigt werden. Erfahre mehr über Drittanbieter-Context-Provider unten.
Verwendung von Context Providern
Context Provider werden typischerweise nahe der Wurzel einer Anwendung gerendert, um globale Anliegen wie das aktuelle Theme zu teilen. Da React Context in Server-Komponenten nicht unterstützt wird, wird der Versuch, einen Context an der Wurzel deiner Anwendung zu erstellen, einen Fehler verursachen:
Um dies zu beheben, erstelle deinen Context und rendere seinen Provider innerhalb einer Client-Komponente:
Deine Server-Komponente kann nun den Provider direkt rendern, da er als Client-Komponente markiert wurde:
Nachdem der Provider an der Wurzel gerendert wurde, können alle anderen Client-Komponenten in deiner App diesen Context nutzen.
Hinweis: Provider sollten so tief wie möglich im Baum gerendert werden – beachte, wie
ThemeProvider
nur{children}
umschließt und nicht das gesamte<html>
-Dokument. Dies macht es für Next.js einfacher, die statischen Teile deiner Server-Komponenten zu optimieren.
Hinweise für Bibliotheksautoren
Ähnlich können Bibliotheksautoren, die Pakete für andere Entwickler erstellen, die "use client"
-Direktive verwenden, um Client-Einstiegspunkte ihres Pakets zu markieren. Dies ermöglicht es Benutzern des Pakets, Paketkomponenten direkt in ihre Server-Komponenten zu importieren, ohne eine umschließende Boundary erstellen zu müssen.
Du kannst dein Paket optimieren, indem du 'use client' tiefer im Baum verwendest, wodurch die importiertenModule Teil des Server-Komponenten-Modulgraphen sein können.
Es ist erwähnenswert, dass einige Bundler die "use client"
-Direktiven entfernen könnten. Ein Beispiel für die Konfiguration von esbuild, um die "use client"
-Direktive beizubehalten, findest du in den Repositories von React Wrap Balancer und Vercel Analytics.
Client-Komponenten
Client-Komponenten im Baum nach unten verschieben
Um die Client-JavaScript-Bundle-Größe zu reduzieren, empfehlen wir, Client-Komponenten in deinem Komponenten-Baum nach unten zu verschieben.
Zum Beispiel könntest du ein Layout haben, das statische Elemente (z.B. Logo, Links etc.) und eine interaktive Suchleiste enthält, die State verwendet.
Anstatt das gesamte Layout zu einer Client-Komponente zu machen, verschiebe die interaktive Logik in eine Client-Komponente (z.B. <SearchBar />
) und behalte dein Layout als Server-Komponente. Das bedeutet, dass du nicht den gesamten Komponenten-JavaScript-Code des Layouts an den Client senden musst.
Props von Server- zu Client-Komponenten übergeben (Serialisierung)
Wenn du Daten in einer Server-Komponente abrufst, möchtest du vielleicht Daten als Props an Client-Komponenten weitergeben. Props, die vom Server an Client-Komponenten übergeben werden, müssen durch React serialisierbar sein.
Wenn deine Client-Komponenten von Daten abhängen, die nicht serialisierbar sind, kannst du Daten auf dem Client mit einer Drittanbieter-Bibliothek abrufen oder auf dem Server mit einem Route Handler.
Verschachtelung von Server- und Client-Komponenten
Bei der Verschachtelung von Client- und Server-Komponenten kann es hilfreich sein, deine UI als Komponenten-Baum zu visualisieren. Beginnend mit dem Root-Layout, das eine Server-Komponente ist, kannst du dann bestimmte Teilbäume von Komponenten auf dem Client rendern, indem du die "use client"
-Direktive hinzufügst.
Innerhalb dieser Client-Teilbäume kannst du immer noch Server-Komponenten verschachteln oder Server Actions aufrufen, jedoch gibt es einige Dinge zu beachten:
- Während eines Request-Response-Lebenszyklus bewegt sich dein Code vom Server zum Client. Wenn du auf dem Client auf Daten oder Ressourcen auf dem Server zugreifen musst, wirst du eine neue Anfrage an den Server stellen - nicht hin und her wechseln.
- Wenn eine neue Anfrage an den Server gestellt wird, werden zuerst alle Server-Komponenten gerendert, einschließlich derer, die in Client-Komponenten verschachtelt sind. Das gerenderte Ergebnis (RSC Payload) wird Referenzen auf die Positionen der Client-Komponenten enthalten. Dann verwendet React auf dem Client den RSC Payload, um Server- und Client-Komponenten zu einem einzigen Baum zusammenzuführen.
- Da Client-Komponenten nach Server-Komponenten gerendert werden, kannst du keine Server-Komponente in ein Client-Komponenten-Modul importieren (da dies eine neue Anfrage zurück an den Server erfordern würde). Stattdessen kannst du eine Server-Komponente als
props
an eine Client-Komponente übergeben. Siehe die Abschnitte nicht unterstütztes Muster und unterstütztes Muster unten.
Nicht unterstütztes Muster: Importieren von Server-Komponenten in Client-Komponenten
Das folgende Muster wird nicht unterstützt. Du kannst keine Server-Komponente in eine Client-Komponente importieren:
Unterstütztes Muster: Server-Komponenten als Props an Client-Komponenten übergeben
Das folgende Muster wird unterstützt. Du kannst Server-Komponenten als Props an eine Client-Komponente übergeben.
Ein gängiges Muster ist die Verwendung der React children
prop, um einen "Slot" in deiner Client-Komponente zu erstellen.
Im folgenden Beispiel akzeptiert <ClientComponent>
eine children
prop:
<ClientComponent>
weiß nicht, dass children
letztendlich mit dem Ergebnis einer Server-Komponente gefüllt wird. Die einzige Verantwortung von <ClientComponent>
ist es zu entscheiden, wo children
platziert werden wird.
In einer übergeordneten Server-Komponente kannst du sowohl die <ClientComponent>
als auch die <ServerComponent>
importieren und <ServerComponent>
als Kind von <ClientComponent>
übergeben:
Mit diesem Ansatz sind <ClientComponent>
und <ServerComponent>
entkoppelt und können unabhängig voneinander gerendert werden. In diesem Fall kann die untergeordnete <ServerComponent>
auf dem Server gerendert werden, lange bevor <ClientComponent>
auf dem Client gerendert wird.
Hinweis:
- Das Muster des "Hochhebens von Inhalten" wurde verwendet, um ein erneutes Rendern einer verschachtelten Kindkomponente zu vermeiden, wenn eine übergeordnete Komponente neu gerendert wird.
- Du bist nicht auf die
children
prop beschränkt. Du kannst jede beliebige Prop verwenden, um JSX zu übergeben.