Ende 2024 erschien die langerwartete stabile Version von React 19. Gut zweieinhalb Jahre hatte es von React 18 bis zur stabilen Version von React 19 gedauert. React 19 wartet mit einigen neuen Funktionen auf, darunter Server Components und Server Functions. Diese wollen wir uns in diesem Blogbeitrag genauer ansehen.
Server und React – was bedeutet das?
React ist als Client-UI-Bibliothek bekannt. Doch seit React 19 gibt es auch einen Serverteil von React. Mit einem Server bezeichnet das React-Team den Vorgang eine Komponente außerhalb des Clients zu rendern. Damit kann sowohl ein Server im klassischen Sinne wie etwa zur Bereitstellung von API-Endpunkten oder ein Buildschritt, welcher etwa auf einem Buildserver gemeint sein. Theoretisch kann man diesen Buildschritt auch auf einer lokalen Maschine durchführen, dann wird diese zum React Server.
Server Components
Server Components werden vom React Server gerendert. Für den Client wird hierbei statisches HTML erzeugt, eine Erzeugung eines JavaScript-Bundles findet nicht statt. Aus diesem Grund ist die Nutzung von sämtlichen Interaktivitäts-/Zustands-APIs wie etwa useState in Server Components nicht gestattet. Wer Interaktivität möchte, muss eine Client Component definieren und diese dann in der Server Component einbinden. Für Client Components wird das benötigte JavaScript erzeugt und an den Client ausgeliefert. Dazu kommen wir später noch.
Der große Vorteil dieser strikten Trennung ist, dass React Features zur Verfügung stellen kann, die speziell für den Serverbetrieb gedacht sind.
In einer klassischen (Client) Component, wie sie bisher in React der Standard war, muss jegliche Kommunikation mit dem Backend über Netzwerkverkehr passieren, wie etwa folgendes Beispiel zeigt:
Im Beispiel wird zwar nur ein fetch-Request abgesetzt, dennoch ist dieser auf Komponentenebene mit jeder Menge overhead verbunden:
Async/await wird auf Komponentenebene nicht unterstützt, stattdessen muss- Ein State mit useState definiert werden
- Async/await kann nur in useEffect genutzt werden
Darüber hinaus beginnt React erst mit der Ausführung des fetch-Requests nach dem ersten Rendering der Komponente. Glücklicherweise ist das Beispiel nur zur Illustration gedacht. J Es zeigt eindrücklich, dass einmalige, asynchrone Aktionen in React Client Components nicht trivial sind. Aus diesem Grund hat die Community selbst Lösungen für dieses Problem geschaffen, wie etwa Tanstack Query.
React Server Components vereinfachen das Beispiel oben gleich mehrfach. Server Components sind statisch, d. h. zustandslos, das Rendering der Komponente findet nur einmal statt. Der React Server kann entscheiden, ob er das Rendering für eine Komponente einmalig, etwa zur Buildzeit oder pro Request durchführt. Aber das statische HTML wird auf dem Client nicht mehr aktualisiert.
Diese Einschränkung hat noch einen weiteren Effekt: Die Komponente ist zustandslos, sie wird nur einmal gerendert, somit kann die Komponentenfunktion selbst auch asynchron sein. Der Server muss sogar wissen, wann die Komponente fertig gerendert wurde, erst dann kann er das HTML an den Client ausliefern.
Dies führt dazu, dass wir unseren Code deutlich vereinfachen können:
Als erstes fällt auf, dass sämtliche Nutzung von useState und useEffect entfällt. Diese sind in Server Components auch nicht zulässig, denn sie sind dynamisch und bilden einen Zustand ab. Wir brauchen sie auch nicht mehr! Wir können auf die Response unseres fetch-Requests direkt in der Komponente warten. Weiterhin habe ich die Fehlerbehandlung verändert: die Komponente ist statisch, damit ist es aus meiner Sicht auch besser, wenn unsere Komponente selbst an den Nutzer:in kommuniziert, dass das Laden der Daten nicht funktioniert hat.
Wie wir sehen, vereinfachen Server Components das Laden von Daten erheblich. Dies kann jedoch noch weiter vereinfacht werden: Wenn wir unsere Komponente auf dem Server rendern und unsere Daten dort im Zugriff haben, so können wir direkt unsere API auf dem Server nutzen:
In diesem Beispiel können wir auf den fetch-Request verzichten und stattdessen direkt die Datenbank-API abfragen. Spätestens jetzt ist klar, dass diese Komponente auf dem Server gerendert werden muss und dass die Datenbank-API nicht per JavaScript an den Client geliefert werden darf/kann. An diesem Punkt zeigt sich die Verschmelzung von Backend und Frontend, wie man es traditionell nur von Technologien wie PHP, ASP.net oder Java kennt. Wir müssen die Netzwerkkommunikation nicht mehr nutzen, um Daten in unsere Komponente zu laden, sondern wir können unsere dafür vorgesehenen APIs nutzen. Und das alles mit Hilfe einer JavaScript-Bibliothek zur Entwicklung von User Interfaces. SMILEY Aus meiner Sicht ist das ein Paradigmenwechsel.
Wie bekommen wir interaktive Komponenten?
Vor der Einführung von Server Components waren alle React Komponenten interaktiv. Sie konnten etwa Hooks wie useState oder useEffect nutzen. Moderne Anwendungen sind jedoch ohne Interaktivität nicht denkbar. Wie bekommen wir also dieses interaktive Verhalten mit React 19 wieder zurück? Mit React Client Komponenten.
Um eine Komponente als Client Komponente zu markieren, muss die Source-Code-Datei der Komponente mit der Direktive „use client“ beginnen. Die Direktive markiert für React, dass der folgende Code auf dem Client ausgeführt werden soll. Dabei ist es egal, ob es sich um eine Komponentendefinition oder anderen Code handelt. Das folgende Beispiel zeigt eine Client Component, welche mit useState einen Counter realisiert:
Übrigens eine Komponente wird auch als Client Komponente bezeichnet, wenn sie eine Kindkomponente einer Client Komponente ist. Damit ist dann allerdings nur die Instanz der Komponente gemeint. Aus einer Server Komponente kann unter bestimmten Umständen somit eine Client Komponente werden.
Mit dem Backend kommunizieren mit Server Functions
Server Functions ermöglichen es aus einer Client Komponente Funktionen auf dem Server aufzurufen. Bis September 2024 wurden Server Functions vom React-Team als Server Actions bezeichnet.
Server Functions sind Funktionen, welche in Client Komponenten importiert und aufgerufen werden können, jedoch auf dem Server ausgeführt werden. Dafür stellt der React Server im Hintergrund einen Endpunkt für die Funktion bereit. Wenn die Client Komponente die Funktion aufruft, sendet React im Hintergrund einen Request an diesen Endpunkt. Server Functions werden durch die Direktive „use server“ markiert. Die Direktive kann entweder in der ersten Zeile der Funktion selbst oder der ersten Zeile der Source-Code-Datei stehen.
Das bedeutet wir können unsere Server-APIs einfach in unseren Client Komponenten aufrufen. Wenn wir unser Counter-Beispiel auf Server Functions umbauen, müssen wir erstmal die passenden Funktionen auf dem Server definieren:
Es gibt eine Funktion getCount(), welche den aktuellen Wert der Variable count zurückgibt. Die zweite Funktion increment(), inkrementiert den Wert von count. Beide Funktionen werden exportiert und sind async. Warum sind sie async? Sie werden im Frontend aufgerufen und das Frontend muss darauf warten, dass der Server antwortet. Das Frontend muss die Funktionen also awaiten, auch wenn die Funktionen selbst nicht blockieren.
Die Funktionen können in unserer Client Komponente jetzt einfach importiert und genutzt werden:
Wir nutzen useState immer noch, um unseren count zu definieren und zu setzen. Den Wert für count laden wir allerdings nur auf Klick auf die Schaltfläche nach. Die andere Schaltfläche aktualisiert unseren count. Dass der Count auf dem Server berechnet wird, kann ganz einfach verifiziert werden: Wenn wir die Seite neu laden und den count erneut abfragen, bekommen wir immer noch den letzten count vom Server zurück.
Implikationen von Server Components und Server Functions
Mit Server Functions und Server Components können wir einfach Funktionalität auf dem Server ausführen. Und das, ohne dass wir Netzwerkkommunikation implementieren müssen. Die Grenzen zwischen Server und Client verschwimmen.
Genau an diesem Punkt muss man innehalten und zwei Dinge beachten: Eine „Spaghettimischung“ von Frontend und Backend ist nicht gut wartbar. Es muss darauf geachtet werden, dass eine wartbare Architektur entsteht. Nicht alles, was möglich ist, passt auch zur Architektur. Vorgaben und Standards sind hier besonders wichtig.
Zweitens muss auf die Sicherheit geachtet werden. Funktionen, die auf dem Server aus dem Frontend aufgerufen werden können, sind nicht per Default sicher. Es sollte genau wie bei Backendendpunkten auch eine Validierung der Eingabewerte vorgenommen werden. Eine Server Function ist im Endeffekt nichts anderes als ein Http-Endpunkt. React nimmt uns nur die lästige Arbeit mit der Netzwerkkommunikation ab.
Trotzdem sorgen Server Functions und Server Components für einen Paradigmenwechsel, da sie React auf den Server und ins Fullstack-Universum holen.
Möchten Sie mehr über moderne React-Entwicklung erfahren? Nehmen Sie Kontakt mit uns auf, um Ihre Ideen zu besprechen!
zurück zur Blogübersicht