Die Webentwicklung hat sich in den letzten Jahren dramatisch weiterentwickelt. Lange Zeit dominierten clientseitige Frameworks, die zwar interaktive Benutzeroberflächen ermöglichten, aber oft auf Kosten der Performance und der Entwicklererfahrung. React, das 2013 als reine Client-Rendering-Bibliothek startete, hat seitdem seinen Weg zu einer vielseitigen Lösung genommen, die auch serverseitiges Rendering unterstützt. Mit der Einführung von React Server Components stellt sich die Frage, wie diese fortschrittliche Technologie in anderen Umgebungen, etwa in Clojure mit dem UIx-Framework, umgesetzt werden kann. Roman Liutikov gibt im ersten Teil seiner Serie „Towards React Server Components in Clojure“ spannende Einblicke in den aktuellen Stand und die Herausforderungen dieser Entwicklung.
Im Ursprung basierte React auf der Idee, die Benutzeroberfläche vollständig im Browser zu rendern, unterstützt durch eine virtuelle DOM-Diffing-Strategie. Die Möglichkeit, Komponenten als HTML-Strings zu serialisieren, eröffnete jedoch neue Ansätze für statische Webseiten und schnelleres Initial-Rendering. Das langfristige Ziel blieb jedoch meist eine Single Page Application (SPA), bei der alle Interaktionen clientseitig erfolgen. Allerdings stieß dieses Modell zunehmend an Grenzen: Der Einbau umfangreicher, nur teilweise dynamischer Inhalte in JavaScript-Bündel führte zu größerem Overhead und schlechterer Performance. Server-Rendering wurde daher populär, um initiale Seiten schneller auszuliefern und die Suchmaschinenoptimierung (SEO) zu verbessern.
Dabei kristallisierten sich zwei Hauptansätze heraus. Zum einen gibt es das klassische Server-Side Rendering (SSR), bei dem die gesamte App serverseitig gerendert wird und dann an den Client geschickt wird, wo die React-App mit Hydration interaktiv wird. Alternativ kann ein sogenanntes Shell-Rendering durchgeführt werden, bei dem ein statisches Grundgerüst vom Server ausgeliefert wird und die dynamischen Inhalte schrittweise im Browser nachgeladen werden. Der neuere, granularere Ansatz nennt sich „Dynamic Islands“ und eignet sich besonders für Websites, die überwiegend aus statischem HTML bestehen, jedoch mehrere kleine, dynamische Komponenten benötigen. Dabei wird das statische Gerüst serverseitig geliefert, während dynamische Teile als unabhängige Komponenten auf dem Client nachgeladen und aktiviert werden.
Dieses Vorgehen erinnert an das sprichwörtliche „Bestreuen“ von Teilen der Webseite mit jQuery-Schnipseln, erlaubt jedoch besseren Performance-Gewinn und klare Trennung von statischem und dynamischem Content. Gleichzeitig bringt der Dynamic-Islands-Ansatz einen erheblichen Mehraufwand mit sich. Die Synchronisation zwischen Server- und Client-Code erfordert manuelle Wartung. Entwickler müssen präzise kontrollieren, welche Komponenten clientseitig interaktiv sind und wie die Datenkommunikation erfolgt. Diese Fragmentierung führt schnell zu schwer wartbaren Codebasen, was React Server Components adressieren wollen.
React Server Components wurden erstmals 2020 als spannende Innovation vorgestellt, die die bisher strikt getrennte Rollenverteilung von Client und Server durchbrechen. Ziel ist eine verbesserte Entwicklererfahrung, die es erlaubt, UI-Komponenten ohne die gewohnte Zweiteilung zu schreiben. Server-Komponenten werden standardmäßig serverseitig ausgeführt, können aber clientseitige Komponenten als Blätter enthalten. Dies ermöglicht eine flexible Mischung aus statischem und dynamischem Content, ohne die zuvor typische Redundanz zwischen Client- und Server-Code. Besonders hervorzuheben ist die Möglichkeit, Server-Komponenten mit asynchronen Serverfunktionen zu verbinden.
Solche Funktionen können direkt im Backend ausgeführt werden und sind über speziell gekennzeichnete Schnittstellen (etwa den "use server" Directive) zuverlässig von Client-Komponenten aus aufrufbar. Auf diese Weise entfällt der Umgang mit expliziten API-Endpunkten für viele Anwendungsfälle, was die Komplexität deutlich verringert. Das klassische Beispiel ist eine Like-Button-Komponente, die auf der Client-Seite einen Klick registriert und dabei eine Serverfunktion aufruft, um den Zähler in der Datenbank zu erhöhen – alles eingebettet in dieselbe React-Komponenten-Architektur. Ein wichtiger Aspekt bei React Server Components ist die Aufteilung der Komponentenhierarchie. Server-Komponenten bilden dabei die äußeren Ebenen, während clientseitige Komponenten die Blätter sind und entsprechende Interaktionen ermöglichen.
Diese Struktur ist zwar eine gängige Praxis, sollte aber nicht als unumstößliche technische Einschränkung gesehen werden. Die klare Trennung sorgt für gutes Rendering-Verhalten und einfachere Zuordnung von Verantwortung zwischen Server und Client. Die komplexe Infrastruktur von React Server Components ist kein Feature, das direkt aus der React-Bibliothek stammt. Stattdessen wird es in Verbindung mit speziellen Frameworks wie Next.js genutzt.
Diese Frameworks verfügen über Build-Systeme, die Client- und Server-Code automatisch trennen, serverseitige Funktionalitäten hosten und eine durchgängige Routing-Lösung für Webanwendungen bieten. Innerhalb dieser Ökosysteme unterstützt React Server Components den nahtlosen Übergang zwischen statischem und dynamischem Rendering. Es existieren nur wenige offene Implementierungen außerhalb von Next.js, dazu zählen OpenNext, Waku und RedwoodJS. Auch der Parcel-Bundler hat jüngst Unterstützung für Server Components ergänzt, was der Technologie zu größerer Verbreitung verhelfen könnte.
Die fehlende offizielle Spezifikation und die noch junge Standardisierung bedeuten allerdings, dass das Verständnis ihrer Funktionsweise häufig noch experimentell und ergebnisoffen ist. Im Kern besteht React Server Components aus einem speziellen Wire-Format, genannt React Flight. Dabei handelt es sich um eine JSON-basierte Datenstruktur, die alle Komponenten inklusive ihrer Typen, Eigenschaften und asynchronen Inhalte kodiert und zwischen Server und Client überträgt. Flight ist auf Streaming ausgelegt, da nicht alle Daten sofort verfügbar sein müssen. Dieses adaptierbare Konzept erlaubt es z.
B., Promises und Streams sauber und effizient abzubilden. React Flight ähnelt auf den ersten Blick HTML, ist aber wesentlich abstrakter und auf Reacts komponentenbasiertes Paradigma zugeschnitten. Die Serialisierung ist stark an die Hiccup-Syntax angelehnt, die in der Clojure-Welt sehr beliebt ist. Sie verwendet unter anderem spezielle Tags für Importe von Client-Komponenten und Platzhalter für asynchrone Werte, was die spätere Nachladung im Browser ermöglicht.
So lässt sich das initiale HTML sofort ausliefern, während weitere dynamische Inhalte in einer asynchronen Folge übermittelt werden. Dieses Vorgehen verbessert die wahrgenommene Ladezeit und erlaubt aufwändige Interaktionen ohne langes Warten auf vollständiges JavaScript. Aus Sicht eines Clojure-Entwicklers bieten React Server Components Chancen und Herausforderungen zugleich. Die Portierung verlangt ein feinkörniges Verständnis sowohl der Serialisierung im React Flight Format als auch der Build- und Laufzeitmechanismen, die Client- und Server-Code nahtlos trennen. Roman Liutikov verfolgt in seinem Projekt eine pragmatische Herangehensweise, bei der der Serialisierer für React Flight vergleichsweise einfach implementiert ist.
Schwieriger sind hingegen Werkzeuge, die die korrekte Trennung von Client- und Server-Code überwachen. Hier helfen Clojure-Charakteristika wie Makros, cljc-Dateien und Reader-Tags. Sein Prototyp basiert auf UIx, einer minimalistischen UI-Bibliothek für Clojure, die serverseitige Komponenten mit clientseitigen Elementen verbindet. Ein Beispiel ist eine Hacker News Demo, bei der die Server-Komponenten Story Listen laden und darstellen, und clientseitige Komponenten wie Vote-Buttons eingebettet sind. Dadurch bleibt die Anwendung klein, performant und doch interaktiv.
Client-Komponenten werden in UIx durch das Meta-Tag ^:client gekennzeichnet und müssen in .cljc Dateien definiert sein, um sowohl von Clojure als auch ClojureScript genutzt zu werden. Ebenso sind serverseitige Funktionen direkt als Aktionen definiert, die mit einer passenden Macro-Unterstützung im Backend ausgeführt werden. Die Kommunikation zwischen Client und Server erfolgt über einen spezialisierten API-Endpunkt, wobei das Ergebnis für den Client serialisiert und aktualisiert wird. Das UIx-Projekt kombiniert eine flexible Kombination aus HTTP-Server (httpkit), Routing (reitit) und Core.
async für asynchrone Kommunikation. Diese modular aufgeteilte Umsetzung ist ein bewusster Kontrast zu den stark eingebetteten Lösungen in der JavaScript-Welt. Das Ziel ist eine offene Plattform mit gutem Entwicklungserlebnis, die sich einfach an individuelle Anforderungen anpassen lässt. Zusammenfassend entsteht mit der Portierung von React Server Components nach Clojure ein vielversprechendes Modell moderner Webentwicklung. Es ermöglicht, dynamische Nutzeroberflächen zu bauen, die serverseitigen Content sinnvoll mit clientseitiger Interaktivität verbinden.