Rendering im Web

Wo sollten Logik und Rendering in unseren Anwendungen implementiert werden? Sollten wir serverseitiges Rendering nutzen? Wie sieht es mit der Rehydrierung aus? Lass uns ein paar Antworten finden!

Als Entwickler stehen wir häufig vor Entscheidungen, die die gesamte Architektur unserer Anwendungen betreffen. Eine der wichtigsten Entscheidungen, die Webentwickler treffen müssen, ist die Implementierung von Logik und Rendering in ihrer App. Dies kann schwierig sein, da es viele verschiedene Möglichkeiten zur Erstellung einer Website gibt.

Unser Verständnis für diesen Bereich basiert auf unseren Bemühungen, in den letzten Jahren mit großen Websites in Chrome zu kommunizieren. Generell empfehlen wir Entwicklern, statt einer vollständigen Rehydrierung ein serverseitiges oder statisches Rendering in Betracht zu ziehen.

Um die Architekturen, aus denen wir uns bei dieser Entscheidung wählen, besser zu verstehen, müssen wir jeden Ansatz genau kennen und eine einheitliche Terminologie verwenden, wenn wir über sie sprechen. Anhand der Unterschiede zwischen diesen Ansätzen werden die Vor- und Nachteile des Renderings im Web anhand der Leistung deutlich.

Terminologie

Rendering

  • Serverseitiges Rendering (SSR): Mit dieser Methode wird eine clientseitige oder universelle App in HTML auf dem Server gerendert.
  • Clientseitiges Rendering (CSR): Rendering einer Anwendung in einem Browser über JavaScript, um das DOM zu ändern.
  • Rehydration:JavaScript-Ansichten werden auf dem Client gestartet, sodass sie die DOM-Baumstruktur und die Daten des vom Server gerenderten HTML-Codes wieder verwenden.
  • Pre-Rendering: Ausführen einer clientseitigen Anwendung zum Build-Zeitpunkt, um ihren Anfangszustand als statischen HTML-Code zu erfassen.

Leistung

Serverseitiges Rendering

Beim serverseitigen Rendering wird als Reaktion auf die Navigation der vollständige HTML-Code für eine Seite auf dem Server generiert. Dadurch werden zusätzliche Umläufe beim Datenabruf und zur Vorlagenerstellung auf dem Client vermieden, da dieser verarbeitet wird, bevor der Browser eine Antwort erhält.

Das serverseitige Rendering erzeugt in der Regel ein schnelles FCP. Durch die Ausführung der Seitenlogik und des Renderings auf dem Server wird verhindert, dass zu viel JavaScript an den Client gesendet wird. Dadurch wird das TBT einer Seite reduziert, was auch zu einer niedrigeren INP führen kann, da der Hauptthread beim Seitenaufbau nicht so oft blockiert wird. Wenn der Hauptthread seltener blockiert wird, haben Nutzerinteraktionen mehr Gelegenheiten, früher ausgeführt zu werden. Dies ergibt Sinn, da beim serverseitigen Rendering eigentlich nur Text und Links an den Browser des Nutzers gesendet werden. Dieser Ansatz eignet sich gut für eine Vielzahl von Geräte- und Netzwerkbedingungen und bietet interessante Browseroptimierungen wie das Parsen von Dokumenten.

Diagramm, das das serverseitige Rendering und die JS-Ausführung mit Auswirkungen auf FCP und TTI zeigt.

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 Drittanbieter-JS nicht vermieden werden kann, können Sie durch serverseitiges Rendering Ihre eigenen Kosten für JavaScript reduzieren und so mehr Budget für den Rest erhalten. Bei diesem Ansatz gibt es jedoch einen möglichen Nachteil: Die Generierung von Seiten auf dem Server nimmt Zeit in Anspruch, was zu einer höheren TTFB führen kann.

Ob serverseitiges Rendering für Ihre Anwendung ausreicht, hängt größtenteils von der Art der Umgebung ab, die Sie erstellen. Es wird schon lange diskutiert darüber, welche Anwendungen von serverseitigem und clientseitigem Rendering richtig angewendet werden, aber bedenken Sie, dass Sie sich für das serverseitige Rendering für einige Seiten entscheiden können und für andere nicht. Auf einigen Websites wurden erfolgreich Hybrid-Rendering-Verfahren eingeführt. Die relativ statischen Landingpages von Netflix werden per 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 schnell geladen werden.

Viele moderne Frameworks, Bibliotheken und Architekturen ermöglichen es, ein und dieselbe Anwendung sowohl auf dem Client als auch auf dem Server zu rendern. Diese Verfahren können für das serverseitige Rendering verwendet werden. Es ist jedoch wichtig zu beachten, dass Architekturen, bei denen das Rendering sowohl auf dem Server als auch auf dem Client erfolgt, ihre eigene Lösungsklasse mit sehr unterschiedlichen Leistungsmerkmalen und Kompromissen sind. React-Nutzer können Server-DOM APIs oder darauf basierende Lösungen wie Next.js für das serverseitige Rendering verwenden. Vue-Nutzer finden weitere Informationen im Leitfaden zum serverseitigen Rendering oder zu Nuxt von Vue. Angular hat Universal. Bei den meisten beliebten Lösungen wird jedoch eine Form der Flüssigkeitszufuhr eingesetzt. Mache dich also mit der Vorgehensweise vertraut, bevor du ein Tool auswählst.

Statisches Rendering

Das statische Rendering erfolgt während der Build-Erstellung. Dieser Ansatz bietet einen schnellen FCP-Wert, aber auch eine niedrigere TBT und niedrigere INP, vorausgesetzt, die Menge an clientseitigem JS ist begrenzt. Anders als beim serverseitigen Rendern erreicht sie auch eine konsistent schnelle TTFB, da der HTML-Code für eine Seite nicht dynamisch auf dem Server generiert werden muss. Im Allgemeinen bedeutet das statische Rendering, dass im Voraus für jede URL eine separate HTML-Datei erstellt wird. Mit im Voraus generierten HTML-Antworten können statische Renderings auf mehreren CDNs bereitgestellt werden, um das Edge-Caching zu nutzen.

Diagramm, das das statisches Rendering und die optionale JS-Ausführung mit Auswirkungen auf FCP und TTI zeigt.

Lösungen für statisches Rendering gibt es in allen Formen und Größen. Tools wie Gatsby sollen Entwicklern das Gefühl geben, dass ihre App dynamisch und nicht als Build-Schritt generiert wird. Tools zum Generieren statischer Websites wie 11ty, Jekyll und Metalsmith nutzen ihren statischen Charakter und bieten einen eher vorlagenbasierten Ansatz.

Einer der Nachteile des statischen Renderings besteht darin, dass für jede mögliche URL einzelne HTML-Dateien generiert werden müssen. Dies kann schwierig oder sogar unmöglich sein, wenn Sie die URLs dieser URLs nicht im Voraus vorhersagen können oder wenn es sich um Websites mit vielen eindeutigen Seiten handelt.

React-Nutzer sind vielleicht 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: Statische Seiten sind interaktiv, ohne dass viel clientseitiges JavaScript ausgeführt werden muss. Pre-Rendering verbessert hingegen den FCP-Wert einer Single-Page-Anwendung, die auf dem Client gestartet werden muss, damit die Seiten wirklich interaktiv sind.

Wenn du dir nicht sicher bist, ob es sich bei einer bestimmten Lösung um statisches Rendering oder Pre-Rendering handelt, deaktiviere JavaScript und lade die Seite, die du testen möchtest. Bei statisch gerenderten Seiten sind die meisten Funktionen auch ohne aktiviertes JavaScript verfügbar. Für vorab gerenderte Seiten können zwar noch einige grundlegende Funktionen wie Links vorhanden sein, der Großteil der Seite ist jedoch inaktiv.

Ein weiterer nützlicher Test ist die Netzwerkdrosselung in den Chrome-Entwicklertools und zu beobachten, wie viel JavaScript heruntergeladen wurde, bevor eine Seite interaktiv wird. Das Pre-Rendering erfordert in der Regel mehr JavaScript, um interaktiv zu werden, und dass JavaScript tendenziell komplexer ist als der Ansatz der Progressive-Enhancement, der beim statischen Rendering verwendet wird.

Serverseitiges und statisches Rendering im Vergleich

Serverseitiges Rendering ist kein Allheilmittel – seine Dynamik kann erhebliche Kosten beim Computing verursachen. Viele serverseitige Rendering-Lösungen werden nicht vorzeitig geleert, können die 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 Singlethread ist. Neuere React-Server-DOM-APIs mit Unterstützung für 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.

Damit das serverseitige Rendering richtig funktioniert, müssen Sie unter anderem eine Lösung für das Komponenten-Caching finden oder entwickeln, den Arbeitsspeicherverbrauch verwalten, Techniken zur Erinnerung anwenden. Im Allgemeinen wird dieselbe Anwendung mehrmals verarbeitet bzw. neu erstellt – einmal auf dem Client und einmal auf dem Server. Auch wenn beim serverseitigen Rendering etwas früher angezeigt werden kann, bedeutet dies nicht plötzlich, dass weniger Arbeit erforderlich ist. Wenn Sie nach Eingang einer servergenerierten HTML-Antwort auf dem Client viel Arbeit auf dem Client haben, kann dies dennoch zu einem höheren TBT und INP für Ihre Website führen.

Beim serverseitigen Rendering wird HTML-on-Demand für jede URL generiert, kann aber länger dauern als das reine Ausliefern statischer gerenderter Inhalte. Wenn Sie zusätzliche Arbeit leisten können, können serverseitiges Rendering und HTML-Caching die Renderingzeit des Servers erheblich reduzieren. Der Vorteil des serverseitigen Renderings ist die Möglichkeit, mehr "Live"-Daten abzurufen und auf einen umfassenderen Satz von Anfragen zu reagieren als mit statischem Rendering. Seiten, die eine Personalisierung erfordern, sind ein konkretes Beispiel für die Art von Anfrage, die bei statischem Rendering nicht gut funktionieren würde.

Das serverseitige Rendering kann beim Erstellen einer PWA auch interessante Entscheidungen treffen: Ist es besser, ganzseitige Service Worker-Caching zu verwenden oder nur einzelne Inhalte vom Server zu rendern?

Clientseitiges Rendering

Beim clientseitigen Rendern werden Seiten mit JavaScript direkt im Browser gerendert. Die gesamte Logik, das Abrufen von Daten, die Vorlagenerstellung und die Weiterleitung 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 aber auch Nachteile mit sich bringt.

Clientseitiges Rendering ist für Mobilgeräte manchmal schwierig und schnell. Bei minimalem Aufwand kann das clientseitige Rendering der Leistung eines reinen serverseitigen Renderings nahekommen. Dabei wird ein eng gefasstes JavaScript-Budget beibehalten und mit möglichst wenigen Umläufen einen Mehrwert erzielt. Mit <link rel=preload> können wichtige Skripts und Daten früher bereitgestellt werden, sodass der Parser schneller für Sie arbeitet. Muster wie PRPL sollten ebenfalls ausgewertet werden, damit die erste und nachfolgende Navigation sofort erscheint.

Diagramm, das das clientseitige Rendering mit Auswirkungen auf FCP und TTI zeigt.

Der größte Nachteil des clientseitigen Renderings besteht darin, dass die erforderliche Menge an JavaScript tendenziell zunimmt, wenn eine Anwendung wächst. 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.

Bei Erfahrungen mit clientseitigem Rendering, die auf großen JavaScript-Bundles basieren, 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 konfigurieren, dass nur das, was Sie brauchen, zum richtigen Zeitpunkt bereitgestellt wird. Für Erlebnisse mit wenig oder gar keiner Interaktivität kann serverseitiges Rendering eine skalierbarere Lösung für diese Probleme darstellen.

Wenn Sie einseitige Anwendungen erstellen und dabei die wichtigsten Bereiche der Benutzeroberfläche identifizieren möchten, die von den meisten Seiten genutzt werden, können Sie die Anwendungsshell-Caching-Technik anwenden. In Kombination mit Service Workern kann dies die wahrgenommene Leistung bei wiederholten Besuchen erheblich verbessern, da der HTML-Code der Anwendung und die zugehörigen Abhängigkeiten sehr schnell über CacheStorage geladen werden können.

Server- und clientseitiges Rendering durch Rehydration kombinieren

Dabei wird versucht, die Vor- und Nachteile zwischen clientseitigem und serverseitigem Rendering auszugleichen. Navigationsanfragen wie das vollständige Laden oder Neuladen einer Seite werden von einem Server verarbeitet, der die Anwendung in HTML rendert. Dann werden das für das Rendering verwendete JavaScript und die Daten in das resultierende Dokument eingebettet. Bei sorgfältiger Umsetzung wird dadurch ein ebenso schnelles FCP wie beim serverseitigen Rendering erreicht, das dann durch das erneute Rendern im Client mithilfe einer Technik namens (Re)hydratisierung wieder „aufsteigt“. Dies ist eine effektive Lösung, kann jedoch erhebliche Leistungsnachteile mit sich bringen.

Der größte Nachteil des serverseitigen Renderings mit Rehydrierung ist, dass es erhebliche negative Auswirkungen auf TBT und INP haben kann, auch wenn es FCP verbessert. Serverseitig gerenderte Seiten können scheinbar geladen und interaktiv erscheinen. Sie können jedoch erst auf Eingaben reagieren, wenn die clientseitigen Skripts für die Komponenten ausgeführt und Event-Handler angehängt wurden. Dies kann auf einem Mobilgerät einige Sekunden oder sogar Minuten dauern.

Vielleicht haben Sie das selbst schon erlebt. Nach einer gewissen Zeit, nachdem eine Seite geladen wurde, geschieht durch Klicken oder Tippen nichts. Das wird schnell frustrierend, da sich die Nutzenden fragen, warum bei der Interaktion mit der Seite nichts passiert.

Ein Problem mit der Rehydrierung: eine App zum Preis von zwei

Rehydratisierungsprobleme sind oft schlimmer als verzögerte Interaktivität aufgrund von JavaScript. Damit das clientseitige JavaScript dort weitermachen kann, wo der Server aufgehört hat, ohne alle Daten, die der Server zum Rendern des HTML-Codes verwendet hat, erneut anzufordern, werden bei aktuellen serverseitigen Rendering-Lösungen die Antwort von den Datenabhängigkeiten einer UI in der Regel als Skript-Tags in das Dokument eingefügt. Das resultierende HTML-Dokument enthält eine große Duplizierung:

HTML-Dokument mit serieller UI, Inline-Daten und einem Bundle.js-Skript

Wie Sie sehen, gibt der Server als Antwort auf eine Navigationsanfrage 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.

Von Leistungsmesswerten, die von echten Websites durch serverseitiges Rendering und Rehydrierung erfasst werden, sollte davon abgeraten werden. Letztlich hängt der Grund mit der Nutzererfahrung zusammen: Es ist äußerst leicht, Nutzer in einem „unheimlichen Tal“ zu hinterlassen, in dem die Interaktivität fehlt, obwohl die Seite bereit zu sein scheint.

Diagramm, das das Client-Rendering zeigt, das sich negativ auf die TTI auswirkt.

Es besteht jedoch die Hoffnung auf ein serverseitiges Rendering mit Rehydrierung. Kurzfristig kann die TTFB reduziert werden, wenn nur serverseitiges Rendering für stark im Cache speicherbare Inhalte verwendet wird, was zu ähnlichen Ergebnissen wie beim Pre-Rendering führt. Die zukünftige, schrittweise oder teilweise rückwirkende Flüssigkeitszufuhr kann entscheidend sein, um diese Technik in Zukunft rentabler zu machen.

Serverseitiges Streaming und progressive Wiederherstellung

Beim serverseitigen Rendering wurden in den letzten Jahren eine Reihe von Entwicklungen vorgenommen.

Beim serverseitigen Streaming können Sie HTML in Blöcken senden, die der Browser beim Empfang schrittweise rendern kann. Dies kann zu einer schnellen FCP-Methode führen, da Markup schneller bei den Nutzern ankommt. React bedeutet, dass bei Streams, die in [renderToPipeableStream()] asynchron sind, im Vergleich zum synchronen renderToString(), einen Rückdruck gut verarbeitet wird.

Auch die schrittweise Rehydrierung ist eine gute Idee – und mit React ist das gelandet. 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, das erforderlich ist, um Seiten interaktiv zu gestalten, da das clientseitige Upgrade von Teilen der Seite mit niedriger Priorität aufgeschoben werden kann, um zu verhindern, dass der Hauptthread blockiert wird. So können Nutzerinteraktionen früher stattfinden, 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 gelöscht und dann sofort neu erstellt wird. Meist war das der Fall, weil für das erste synchrone clientseitige Rendering Daten erforderlich waren, die nicht ganz fertig waren, z. B. auf die Auflösung eines Promise warten.

Teilweise Rehydrierung

Die teilweise Rehydrierung hat sich als schwierig erwiesen. Dieser Ansatz ist eine Erweiterung des Konzepts der progressiven Rehydrierung. Dabei werden die einzelnen Teile (Komponenten/Ansichten/Bäume), die nach und nach rehydriert werden sollen, analysiert und diejenigen identifiziert, die wenig Interaktivität oder keine Reaktivität zeigen. Für jeden dieser zumeist statischen Teile wird der entsprechende JavaScript-Code dann in inerte Referenzen und dekorative Funktionalitäten umgewandelt, wodurch der clientseitige Aufwand auf fast null reduziert wird.

Bei der teilweisen Flüssigkeitszufuhr sind einige Probleme und Kompromisse verbunden. Dies bringt einige interessante Herausforderungen beim Caching mit sich, und die clientseitige Navigation bedeutet, dass wir nicht davon ausgehen können, dass vom Server gerenderter HTML-Code 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 Anfangs- und Nicht-JS-Navigationen verwenden und dann Ihren Service Worker das Rendern von HTML für Navigationen übernehmen lassen, 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 ermöglicht werden. Dieser Ansatz funktioniert am besten, wenn Sie denselben Vorlagen- und Routingcode für den Server, die Clientseite und den Service Worker verwenden können.

Diagramm eines trisomorphen Renderings, das zeigt, wie ein Browser und ein Service Worker mit dem Server kommunizieren

Überlegungen zur SEO

Teams berücksichtigen bei der Auswahl einer Rendering-Strategie im Web häufig die Auswirkungen der SEO. Das serverseitige Rendering wird oft gewählt, um ein „vollständig aussehendes“ Erlebnis zu bieten, das Crawler problemlos interpretieren können. Crawler können JavaScript zwar verstehen, es gibt jedoch oft Einschränkungen, deren Darstellung Sie sich bewusst machen sollten. Clientseitiges Rendering kann funktionieren, allerdings oft nicht ohne zusätzliche Tests und Arbeitsschritte. Seit Kurzem eignet sich auch das dynamische Rendering, 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 Ihre Erwartungen erfüllt. Sie zeigt eine visuelle Vorschau davon, wie eine Seite dem Google-Crawler angezeigt wird, den gefundenen seriellen HTML-Inhalt (nach der Ausführung von JavaScript) und alle Fehler, die während des Renderings aufgetreten sind.

Screenshot der Benutzeroberfläche zum Test auf Optimierung für Mobilgeräte

Zusammenfassung

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

In einer Infografik wird das Spektrum der in diesem Artikel beschriebenen Optionen dargestellt.

Guthaben

Vielen Dank an alle für ihre Rezensionen und die Inspiration:

Jeffrey Posnick, Houssein Djirdeh, Shubhie Panicker, Chris Harrelson und Sebastian Markbåge