Wo sollten wir Logik und Rendering in unseren Anwendungen implementieren? Sollten wir serverseitiges Rendering verwenden? Wie sieht es mit der Rehydrierung aus? Suchen wir mal ein paar Antworten.
Als Entwickler stehen wir häufig vor Entscheidungen, die die gesamte Architektur unserer Anwendungen betreffen. Eine der zentralen Entscheidungen, die Webentwickler treffen müssen, ist die Implementierung der Logik und des Renderings in ihrer App. Dies kann schwierig sein, da es viele verschiedene Möglichkeiten zum Erstellen einer Website gibt.
Unser Verständnis für diesen Bereich beruht auf unserer Bemühungen, in Chrome in den letzten Jahren mit großen Websites zu interagieren. Ganz allgemein gesagt empfehlen wir Entwicklern, das serverseitige oder statische Rendering anstelle eines Ansatzes zur vollständigen Rehydrierung in Betracht zu ziehen.
Um die Architekturen, aus denen wir uns bei dieser Entscheidung auswählen, besser zu verstehen, müssen wir jeden Ansatz genau verstehen und eine einheitliche Terminologie verwenden, wenn wir darüber sprechen. Die Unterschiede zwischen diesen Ansätzen verdeutlichen anhand der Leistung die Vor- und Nachteile des Renderings im Web.
Terminologie
Rendering
- Serverseitiges Rendering (SSR): Damit wird eine clientseitige oder universelle App in HTML auf dem Server gerendert.
- Clientseitiges Rendering (CSR): Rendern einer Anwendung in einem Browser über JavaScript, um das DOM zu ändern
- Rehydrierung:JavaScript-Ansichten werden auf dem Client gestartet, sodass der DOM-Baum und die Daten des vom Server gerenderten HTML-Codes wiederverwendet werden.
- Pre-Rendering: Ausführen einer clientseitigen Anwendung zum Build-Zeitpunkt, um den Anfangszustand als statischen HTML-Code zu erfassen
Leistung
- Time to First Byte (TTFB):Dies ist die Zeit zwischen dem Klicken auf einen Link und dem ersten eingehenden Inhalt.
- First Contentful Paint (FCP):Der Zeitpunkt, zu dem der angeforderte Inhalt (Artikeltext usw.) sichtbar wird.
- Interaction to Next Paint (INP):Dieser repräsentative Messwert gibt an, ob eine Seite konstant schnell auf Nutzereingaben reagiert.
- Total Blocking Time (TBT):Ein Proxymesswert für INP, der berechnet, wie lange der Hauptthread während des Seitenaufbaus blockiert wurde.
Serverseitiges Rendering
Beim serverseitigen Rendering wird der vollständige HTML-Code für eine Seite auf dem Server als Reaktion auf die Navigation generiert. Dadurch werden zusätzliche Umläufe beim Datenabruf und zum Erstellen von Vorlagen auf dem Client vermieden, da dies verarbeitet wird, bevor der Browser eine Antwort erhält.
Beim serverseitigen Rendering wird in der Regel ein schnelles FCP erzeugt. Durch das Ausführen der Seitenlogik und des Renderings auf dem Server kann vermieden werden, dass zu viel JavaScript an den Client gesendet wird. Dadurch wird die TBT einer Seite reduziert, die auch zu einer niedrigeren INP führen kann, da der Hauptthread beim Seitenaufbau nicht so oft blockiert wird. Wenn der Hauptthread seltener blockiert wird, können Nutzerinteraktionen schneller ausgeführt werden. Das ist sinnvoll, da Sie beim serverseitigen Rendering eigentlich nur Text und Links an den Browser des Nutzers senden. Dieser Ansatz eignet sich für eine Vielzahl von Geräte- und Netzwerkbedingungen und eröffnet interessante Browseroptimierungen wie das Streamen von Dokumenten.

Beim serverseitigen Rendering ist es weniger wahrscheinlich, dass Nutzer auf die Ausführung von CPU-gebundenem JavaScript warten müssen, bevor sie Ihre Website verwenden können. Selbst wenn sich Drittanbieter-JS nicht vermeiden lässt, können Sie durch serverseitiges Rendering die Kosten für Erstanbieter-JavaScript reduzieren und so mehr Budget für den Rest bereitstellen. Allerdings gibt es bei diesem Ansatz einen möglichen Kompromiss: Die Generierung von Seiten auf dem Server nimmt Zeit in Anspruch, was zu einer höheren TTFB führen kann.
Ob das serverseitige Rendering für Ihre Anwendung ausreicht, hängt größtenteils von der Art der Umgebung ab, die Sie erstellen. Die richtige Anwendung des serverseitigen und clientseitigen Renderings wird seit Langem diskutiert. Bedenken Sie jedoch, dass Sie das serverseitige Rendering nur für einige Seiten verwenden können. Einige Websites setzen erfolgreich Hybrid-Rendering-Verfahren ein. Die relativ statischen Landingpages von Netflix werden vom Server gerendert, während das JS für interaktionsintensive Seiten im Vorabruf wird. Dadurch erhöht sich die Wahrscheinlichkeit, dass diese schwereren, clientseitig gerenderten Seiten schneller geladen werden.
Viele moderne Frameworks, Bibliotheken und Architekturen ermöglichen es, dieselbe Anwendung sowohl auf dem Client als auch auf dem Server zu rendern. Diese Techniken können für das serverseitige Rendering verwendet werden. Allerdings sind Architekturen, bei denen das Rendering sowohl auf dem Server als auch auf dem Client erfolgt, eine eigene Lösungsklasse mit sehr unterschiedlichen Leistungsmerkmalen und Kompromissen. React-Nutzer können für das serverseitige Rendering Server-DOM-APIs oder auf ihnen basierende Lösungen wie Next.js verwenden. Vue-Nutzer können sich den Leitfaden zum serverseitigen Rendering von Vue oder in Nuxt ansehen. Angular hat Universal. Bei den meisten gängigen Lösungen wird jedoch eine Form der Flüssigkeitszufuhr eingesetzt. Sie sollten sich daher mit dem verwendeten Ansatz vertraut machen, bevor Sie ein Werkzeug auswählen.
Statisches Rendering
Das statische Rendering findet bei der Erstellung statt. Dieser Ansatz bietet eine schnelle FCP- und niedrigere TBT und INP – vorausgesetzt, die Menge an clientseitigem JS ist begrenzt. Anders als beim serverseitigen Rendern erreicht es auch eine konsistent schnelle TTFB, da der HTML-Code für eine Seite nicht dynamisch auf dem Server generiert werden muss. Im Allgemeinen bedeutet statisches Rendering, dass im Voraus für jede URL eine separate HTML-Datei erstellt wird. Mit vorab generierten HTML-Antworten können statische Renderings auf mehreren CDNs bereitgestellt werden, um das Edge-Caching zu nutzen.

Lösungen für statisches Rendering gibt es in allen möglichen Formen und Größen. Tools wie Gatsby sollen Entwicklern das Gefühl geben, dass ihre App dynamisch gerendert und nicht als Build-Schritt generiert wird. Tools zur Generierung statischer Websites wie 11ty, Jekyll und Metalsmith nutzen ihre statische Natur und bieten einen eher vorlagenbasierten Ansatz.
Einer der Nachteile des statischen Renderings ist, dass einzelne HTML-Dateien für jede mögliche URL generiert werden müssen. Dies kann schwierig oder sogar unmöglich sein, wenn Sie nicht im Voraus vorhersagen können, wie diese URLs aussehen werden, oder bei Websites mit einer großen Anzahl eindeutiger Seiten.
React-Nutzer sind möglicherweise mit Gatsby, dem statischen Export von Next.js oder Navi vertraut. All diese Methoden erleichtern das Erstellen von Seiten mithilfe von Komponenten. Es ist jedoch wichtig, den Unterschied zwischen statischem Rendering und Pre-Rendering zu verstehen: statisch gerenderte Seiten sind interaktiv, ohne dass viel clientseitiges JavaScript ausgeführt werden muss. Das Pre-Rendering verbessert den FCP-Wert einer Single-Page-Anwendung, die auf dem Client gestartet werden muss, damit Seiten wirklich interaktiv sind.
Wenn Sie sich nicht sicher sind, ob eine bestimmte Lösung statisches Rendering oder Pre-Rendering ist, deaktivieren Sie JavaScript und laden Sie die zu testende Seite. Bei statisch gerenderten Seiten sind die meisten Funktionen auch ohne aktiviertes JavaScript verfügbar. Für vorab gerenderte Seiten kann es immer noch einige grundlegende Funktionen wie Links geben, aber der Großteil der Seite ist inaktiv.
Ein weiterer nützlicher Test besteht darin, die Netzwerkdrosselung in den Chrome-Entwicklertools zu verwenden und zu beobachten, wie viel JavaScript heruntergeladen wurde, bevor eine Seite interaktiv wird. Das Pre-Rendering erfordert im Allgemeinen mehr JavaScript, um interaktiv zu werden, und dass JavaScript tendenziell komplexer ist als beim statischen Rendering mit Progressive Enhancement.
Serverseitiges und statisches Rendering im Vergleich
Serverseitiges Rendering ist kein Allheilmittel, da seine dynamische Funktionsweise mit erheblichen Rechenleistungs-Gemeinkosten verbunden ist. Viele serverseitige Rendering-Lösungen werden nicht frühzeitig geleert, können TTFB verzögern oder die gesendeten Daten verdoppeln (z. B. der Inline-Zustand, der von JavaScript auf dem Client verwendet wird). In React kann renderToString()
langsam sein, da es synchron und Single-Threaded ist. Neuere React-Server-DOM-APIs unterstützen Streaming. Dadurch kann der erste Teil einer HTML-Antwort schneller an den Browser gesendet werden, während der Rest noch auf dem Server generiert wird.
Das „richtige“ serverseitige Rendering kann unter anderem bedeuten, eine Lösung für das Komponenten-Caching zu finden oder zu erstellen, den Arbeitsspeicherverbrauch zu verwalten, Memoisierungstechniken anzuwenden und andere Aspekte zu berücksichtigen. Im Allgemeinen wird dieselbe Anwendung mehrmals verarbeitet/neu erstellt – einmal auf dem Client und einmal auf dem Server. Nur weil serverseitiges Rendering früher dazu führen kann, dass etwas früher angezeigt wird, bedeutet dies nicht, dass Sie weniger Arbeit haben. Wenn Sie auf dem Client viel Arbeit haben, nachdem eine servergenerierte HTML-Antwort auf dem Client eingegangen ist, kann dies immer noch zu höheren TBT- und INP-Werten für Ihre Website führen.
Beim serverseitigen Rendering wird HTML-on-Demand für jede URL generiert, es kann jedoch länger dauern als nur statische gerenderte Inhalte bereitzustellen. Wenn Sie zusätzlichen Aufwand schaffen, können serverseitiges Rendering und HTML-Caching die Renderingzeit des Servers erheblich verkürzen. Der Vorteil des serverseitigen Renderings ist die Möglichkeit, mehr Live-Daten abzurufen und auf einen umfassenderen Satz von Anfragen zu antworten als mit statischem Rendering. Seiten, die personalisiert werden müssen, sind ein konkretes Beispiel für einen Anfragetyp, der mit statischem Rendering nicht gut funktioniert.
Auch das serverseitige Rendering kann beim Erstellen einer PWA aufschlussreiche Entscheidungen treffen: Ist es besser, ganzseitiges Service Worker-Caching zu verwenden oder nur einzelne Inhalte vom Server zu rendern?
Clientseitiges Rendering
Beim clientseitigen Rendern werden Seiten direkt im Browser mit JavaScript gerendert. Die gesamte Logik, das Abrufen von Daten, die Vorlagenerstellung und das Routing werden auf dem Client und nicht auf dem Server verarbeitet. Das Ergebnis ist, dass mehr Daten vom Server an das Gerät des Nutzers übergeben werden, was mit eigenen Kompromissen einhergeht.
Clientseitiges Rendering ist für Mobilgeräte nicht immer einfach und schnell. Wenn nur minimaler Aufwand ausgeführt wird, kann das clientseitige Rendering der Leistung eines reinen serverseitigen Renderings annähern. Dabei wird ein straffes JavaScript-Budget beibehalten und mit so wenigen Umläufen wie möglich ein Mehrwert erzielt. Mit <link rel=preload>
können wichtige Skripts und Daten früher bereitgestellt werden, sodass der Parser schneller arbeitet. Muster wie PRPL sind ebenfalls eine Bewertung wert, damit die erste und nachfolgende Navigation sofort funktioniert.

Der größte Nachteil des clientseitigen Renderings besteht darin, dass die erforderliche JavaScript-Menge tendenziell zunimmt, wenn eine Anwendung zunimmt. Dies kann sich negativ auf die INP-Werte einer Seite auswirken. Besonders schwierig wird dies durch das Hinzufügen neuer JavaScript-Bibliotheken, Polyfills und Drittanbietercode, die um die Verarbeitungsleistung konkurrieren und häufig verarbeitet werden müssen, bevor der Inhalt einer Seite gerendert werden kann.
Wenn Sie clientseitiges Rendering verwenden, das auf großen JavaScript-Bundles basiert, sollten Sie eine aggressive Codeaufteilung in Betracht ziehen, um TBT und INP beim Seitenaufbau zu senken. Außerdem sollten Sie JavaScript per Lazy-Loading so laden, dass nur die benötigten Informationen zum richtigen Zeitpunkt bereitgestellt werden. Für Umgebungen mit wenig oder gar keiner Interaktivität kann serverseitiges Rendering eine skalierbarere Lösung für diese Probleme sein.
Wenn Sie Single-Page-Anwendungen erstellen, können Sie die Kernbereiche der Benutzeroberfläche identifizieren, die von den meisten Seiten genutzt werden. Hierzu können Sie die Anwendungs-Shell-Caching-Technik anwenden. In Kombination mit Service Workern kann dies die wahrgenommene Leistung bei wiederholten Besuchen erheblich verbessern, da der HTML-Code der Anwendungs-Shell und die zugehörigen Abhängigkeiten sehr schnell von CacheStorage
geladen werden können.
Serverseitiges und clientseitiges Rendering durch Rehydration kombinieren
Bei diesem Ansatz sollen die Kompromisse zwischen clientseitigem und serverseitigem Rendering durch beides ausgeglichen werden. Navigationsanfragen wie das vollständige Laden oder Neuladen einer Seite werden von einem Server verarbeitet, der die Anwendung in HTML rendert. Anschließend werden der JavaScript-Code und die für das Rendern verwendeten Daten in das resultierende Dokument eingebettet. Bei sorgfältiger Vorgehensweise wird so ein schnelles FCP wie beim serverseitigen Rendering erreicht, das dann mithilfe einer Technik namens (Re)Hydration wieder auf dem Client gerendert wird. Dies ist eine effektive Lösung, kann jedoch erhebliche Leistungseinbußen mit sich bringen.
Der größte Nachteil des serverseitigen Renderings mit Rehydration ist, dass es erhebliche negative Auswirkungen auf TBT und INP haben kann, selbst wenn es FCP verbessert. Serverseitig gerenderte Seiten können täuschend geladen und interaktiv wirken, können aber erst dann auf Eingaben reagieren, wenn die clientseitigen Skripts für Komponenten ausgeführt und Event-Handler angehängt wurden. Das kann auf Mobilgeräten einige Sekunden oder sogar Minuten dauern.
Vielleicht haben Sie dies selbst schon erlebt. Nach einer gewissen Zeit, nachdem eine Seite geladen wurde, geschieht durch Klicken oder Tippen nichts. Dies wird schnell frustrierend, da sich die Nutzenden fragen, warum nichts passiert, wenn sie versuchen, mit der Seite zu interagieren.
Ein Problem mit der Rehydrierung: Eine App zum Preis von zwei
Probleme bei der Rehydrierung können oft schlimmer sein als verzögerte Interaktivität aufgrund von JavaScript. Damit das clientseitige JavaScript genau dort weitermachen kann, wo der Server aufgehört hat, ohne alle Daten, die der Server zum Rendern des HTML-Codes verwendet hat, erneut anfordern zu müssen, serialisieren aktuelle serverseitige Rendering-Lösungen die Antwort von den Datenabhängigkeiten einer Benutzeroberfläche in der Regel als Skript-Tags in das Dokument. Das resultierende HTML-Dokument weist eine hohe Anzahl von Duplizierungen auf:

Wie Sie sehen, gibt der Server als Antwort auf eine Navigationsanforderung eine Beschreibung der Anwendungs-UI zurück. Er gibt aber auch die Quelldaten zurück, die zum Erstellen dieser UI verwendet wurden, sowie eine vollständige Kopie der UI-Implementierung, die dann auf dem Client gestartet wird. Erst nachdem bundle.js
das Laden und Ausführen abgeschlossen hat, wird diese UI interaktiv.
Leistungsmesswerte, die mithilfe von serverseitigem Rendering und Rehydrierung von echten Websites erfasst werden, lassen darauf schließen, dass von dieser Verwendung abgeraten wird. Letztlich ist dies auf die Nutzererfahrung zurückzuführen: Es ist äußerst leicht, Nutzer in ein „unheimliches Tal“ zu versetzen, in dem die Interaktivität zu fehlen scheint, obwohl die Seite anscheinend bereit ist.

Es besteht jedoch die Hoffnung auf ein serverseitiges Rendering mit Rehydration. Kurzfristig kann die TTFB reduziert werden, wenn nur serverseitiges Rendering für stark im Cache speicherbare Inhalte verwendet wird. Dies führt zu ähnlichen Ergebnissen wie beim Pre-Rendering. Die inkrementelle, schrittweise oder teilweise rehydrierte Flüssigkeit kann der Schlüssel sein, um diese Technik in Zukunft rentabler zu machen.
Serverseitiges Streaming und schrittweise Rehydration
Beim serverseitigen Rendering gab es in den letzten Jahren eine Reihe von Entwicklungen.
Beim serverseitigen Streaming-Rendering können Sie HTML-Code in Blöcken senden, die der Browser beim Empfang schrittweise rendern kann. Dies kann zu einer schnellen FCP-Anfrage führen, da Markup den Nutzern schneller eintrifft. In React bedeutet ein asynchroner Stream in [renderToPipeableStream()
] – im Vergleich zum synchronen renderToString()
–, dass der Rückstau gut gehandhabt wird.
Auch eine schrittweise Rehydrierung ist eine Überzeugung, die mit React bereits erfolgreich war. Bei diesem Ansatz werden im Laufe der Zeit einzelne Teile einer vom Server gerenderten Anwendung "gestartet", anstatt wie bisher die gesamte Anwendung auf einmal zu initialisieren. Dies kann dazu beitragen, die Menge an JavaScript zu reduzieren, die erforderlich ist, um Seiten interaktiv zu machen, da das clientseitige Upgrade von Teilen der Seite mit niedriger Priorität verzögert werden kann, um das Blockieren des Hauptthreads zu verhindern, sodass Nutzerinteraktionen früher erfolgen können, nachdem der Nutzer sie initiiert hat.
Die progressive Wiederherstellung kann auch dazu beitragen, eines der häufigsten Fallstricke beim serverseitigen Rendering zu vermeiden, bei dem ein vom Server gerenderter DOM-Baum zerstört und dann sofort neu erstellt wird. Das ist meistens der Fall, weil für das erste synchrone clientseitige Rendering Daten erforderlich waren, die noch nicht bereit waren, z. B. auf die Auflösung eines Promise
warten.
Teilweise Rehydrierung
Teilweise Rehydrierung hat sich als schwierig erwiesen. Dieser Ansatz ist eine Erweiterung der Idee der progressiven Rehydrierung, bei der die einzelnen Teile (Komponenten/Ansichten/Bäume), die nach und nach rehydriert werden sollen, analysiert und diejenigen identifiziert werden, die wenig oder gar keine Reaktivität zeigen. Für jeden dieser zumeist statischen Teile wird der entsprechende JavaScript-Code dann in inerte Referenzen und dekorative Funktionalität umgewandelt, wodurch der clientseitige Aufwand auf fast null reduziert wird.
Der Ansatz der teilweisen Flüssigkeitszufuhr bringt auch Nachteile mit sich und bringt auch Nachteile mit sich. Dies birgt einige interessante Herausforderungen beim Caching. Bei der clientseitigen Navigation können wir nicht davon ausgehen, dass vom Server gerendertes HTML für inerte Teile der Anwendung ohne vollständiges Laden der Seite verfügbar ist.
Trisomorphes Rendering
Wenn Service Worker eine Option für Sie sind, könnte auch ein „trisomorphes“ Rendering von Interesse sein. Mit dieser Technik können Sie serverseitiges Streaming für die Anfangs- und Nicht-JS-Navigation verwenden und Ihren Service Worker das Rendern von HTML für Navigationen übernehmen, nachdem es installiert wurde. So können im Cache gespeicherte Komponenten und Vorlagen auf dem neuesten Stand gehalten und Navigationen im SPA-Stil zum Rendern neuer Ansichten in derselben Sitzung aktiviert werden. Dieser Ansatz funktioniert am besten, wenn Sie denselben Vorlagen- und Routingcode zwischen dem Server, der Clientseite und dem Service Worker teilen können.

Überlegungen zur SEO
Teams berücksichtigen bei der Auswahl einer Rendering-Strategie im Web oft die Auswirkungen der SEO. Das serverseitige Rendering wird häufig gewählt, um ein „vollständiges“ Erlebnis zu bieten, das Crawler problemlos interpretieren können. Crawler können JavaScript verstehen, es gibt jedoch häufig Einschränkungen, die Sie beim Rendern beachten sollten. Clientseitiges Rendering kann funktionieren, allerdings ohne zusätzliche Tests und Arbeitsaufwand. In letzter Zeit bietet sich auch das dynamische Rendering an, wenn Ihre Architektur stark von clientseitigem JavaScript abhängt.
Im Zweifelsfall ist das Tool zum Test auf Optimierung für Mobilgeräte von unschätzbarem Wert, um zu testen, ob der von Ihnen gewählte Ansatz funktioniert, was Sie sich erhoffen. Sie sehen eine visuelle Vorschau davon, wie eine Seite für den Google-Crawler angezeigt wird, welche serialisierten HTML-Inhalte (nach der Ausführung von JavaScript) gefunden wurden und welche Fehler beim Rendern aufgetreten sind.

Zusammenfassung
Wenn Sie sich für einen Renderingansatz entscheiden, sollten Sie Engpässe messen und verstehen. Überlegen Sie, ob Sie diese Ziele mit statischem oder serverseitigem Rendering erreichen können. Es ist völlig in Ordnung, HTML-Code mit minimalem JavaScript-Code zu senden, damit er interaktiv wird. Hier ist eine praktische Infografik, die das Server-Client-Spektrum zeigt:

Guthaben
Vielen Dank an alle für ihre Bewertungen und Anregungen:
Jeffrey Posnick, Houssein Djirdeh, Shubhie Panicker, Chris Harrelson und Sebastian Markbåge