Eine der wichtigsten Entscheidungen, die Webentwickler treffen müssen, ist, wo sie Logik und Rendering in ihrer Anwendung implementieren. Das kann schwierig sein, da es so viele Möglichkeiten gibt, eine Website zu erstellen.
Unsere Kenntnisse in diesem Bereich basieren auf unserer Arbeit in Chrome, bei der wir in den letzten Jahren mit großen Websites gesprochen haben. Generell empfehlen wir Entwicklern, serverseitiges oder statisches Rendering anstelle einer vollständigen Rehydration zu verwenden.
Um die Architekturen besser zu verstehen, aus denen wir bei dieser Entscheidung auswählen, benötigen wir ein solides Verständnis der einzelnen Ansätze und eine einheitliche Terminologie, die wir bei der Beschreibung verwenden. Die Unterschiede zwischen den Rendering-Ansätzen veranschaulichen die Vor- und Nachteile des Renderings im Web aus der Perspektive der Seitenleistung.
Terminologie
Zuerst definieren wir einige Begriffe, die wir verwenden werden.
Rendering
- Serverseitiges Rendering (SSR)
- Eine App auf dem Server rendern, um HTML anstelle von JavaScript an den Client zu senden.
- Clientseitiges Rendering (CSR)
- Eine App in einem Browser rendern und das DOM mit JavaScript ändern.
- Rehydration
- JavaScript-Ansichten auf dem Client „starten“, damit der DOM-Baum und die Daten des serverseitig gerenderten HTML wiederverwendet werden.
- Pre-Rendering
- Eine clientseitige Anwendung zur Laufzeit ausführen, um ihren ursprünglichen Zustand als statische HTML-Datei zu erfassen.
Leistung
- Zeit bis zum ersten Byte (TTFB)
- Die Zeit zwischen dem Klicken auf einen Link und dem Laden des ersten Inhaltsbytes auf der neuen Seite.
- First Contentful Paint (FCP)
- Der Zeitpunkt, zu dem angeforderte Inhalte (z. B. Artikeltext) sichtbar werden.
- Interaction to Next Paint (INP)
- Ein repräsentativer Messwert, der beurteilt, ob eine Seite schnell und zuverlässig auf Nutzereingaben reagiert.
- Gesamte Blockierzeit (Total Blocking Time, TBT)
- Proxy-Messwert für INP, mit dem berechnet wird, wie lange der Haupt-Thread während des Seitenaufbaus blockiert war.
Serverseitiges Rendering
Beim serverseitigen Rendering wird die vollständige HTML-Seite für eine Seite auf dem Server in Reaktion auf die Navigation generiert. Dadurch werden zusätzliche Aufrufe für das Abrufen von Daten und das Erstellen von Vorlagen auf dem Client vermieden, da der Renderer diese verarbeitet, bevor der Browser eine Antwort erhält.
Beim serverseitigen Rendering wird in der Regel ein schneller FCP erzielt. Wenn Sie die Seitenlogik und das Rendering auf dem Server ausführen, müssen Sie nicht viel JavaScript an den Client senden. Dies trägt dazu bei, die TBT einer Seite zu reduzieren, was auch zu einem niedrigeren INP führen kann, da der Hauptthread beim Laden der Seite nicht so oft blockiert wird. Wenn der Hauptthread seltener blockiert wird, können Nutzerinteraktionen schneller ausgeführt werden. Das ist sinnvoll, da beim serverseitigen Rendering nur Text und Links an den Browser des Nutzers gesendet werden. Dieser Ansatz kann für eine Vielzahl von Geräte- und Netzwerkbedingungen gut funktionieren und bietet interessante Browseroptimierungen wie das Streaming-Dokument-Parsing.
Beim serverseitigen Rendering müssen Nutzer seltener warten, bis CPU-intensives JavaScript ausgeführt wurde, bevor sie Ihre Website verwenden können. Auch wenn Sie JavaScript von Drittanbietern nicht vermeiden können, können Sie mit serverseitigem Rendering Ihre eigenen JavaScript-Kosten senken und so mehr Budget für den Rest haben. Bei diesem Ansatz gibt es jedoch einen potenziellen Nachteil: Das Generieren von Seiten auf dem Server dauert seine Zeit, was die TTFB der Seite erhöhen kann.
Ob das serverseitige Rendering für Ihre Anwendung ausreicht, hängt weitgehend davon ab, welche Art von Anwendung Sie entwickeln. Es gibt eine lange Debatte über die richtige Anwendung von serverseitigem und clientseitigem Rendering. Sie können jedoch immer entscheiden, das serverseitige Rendering für einige Seiten und nicht für andere zu verwenden. Einige Websites haben Hybrid-Rendering-Techniken erfolgreich eingesetzt. Netflix rendert beispielsweise seine relativ statischen Landingpages serverseitig und prefetching das JS für Seiten mit vielen Interaktionen vorab, damit diese schwereren clientseitig gerenderten Seiten schneller geladen werden können.
Mit vielen modernen Frameworks, Bibliotheken und Architekturen können Sie dieselbe Anwendung sowohl auf dem Client als auch auf dem Server rendern. Sie können diese Techniken für das serverseitige Rendering verwenden. Architekturen, bei denen das Rendering sowohl auf dem Server als auch auf dem Client erfolgt, sind jedoch eine eigene Lösungsklasse mit sehr unterschiedlichen Leistungsmerkmalen und Kompromissen. React-Nutzer können für das serverseitige Rendering Server-DOM-APIs oder darauf basierende Lösungen wie Next.js verwenden. Vue-Nutzer können den Leitfaden zum serverseitigen Rendering von Vue oder Nuxt verwenden. Angular hat Universal. Die meisten gängigen Lösungen nutzen jedoch eine Form der Hydratisierung. Achten Sie daher darauf, welche Ansätze Ihr Tool verwendet.
Statisches Rendering
Das statische Rendering erfolgt während der Buildzeit. Dieser Ansatz bietet eine schnelle FCP sowie eine geringere TBT und INP, sofern Sie die Menge an clientseitigem JS auf Ihren Seiten begrenzen. Im Gegensatz zum serverseitigen Rendering wird außerdem eine konstant schnelle TTFB erreicht, da das HTML für eine Seite nicht dynamisch auf dem Server generiert werden muss. Im Allgemeinen bedeutet statisches Rendering, dass für jede URL im Voraus eine separate HTML-Datei erstellt wird. Mit vorab generierten HTML-Antworten können Sie statische Renderings in mehreren CDNs bereitstellen, um das Edge-Caching zu nutzen.
Lösungen für das statische Rendering gibt es in allen Formen und Größen. Tools wie Gatsby sollen Entwicklern das Gefühl geben, dass ihre Anwendung dynamisch gerendert und nicht als Buildschritt generiert wird. Tools zur Generierung statischer Websites wie 11ty, Jekyll und Metalsmith nutzen ihre statische Natur und bieten einen eher an Vorlagen orientierten Ansatz.
Ein Nachteil des statischen Renderings ist, dass für jede mögliche URL separate HTML-Dateien generiert werden müssen. Das kann schwierig oder sogar unmöglich sein, wenn Sie diese URLs nicht im Voraus vorhersagen können, oder bei Websites mit einer großen Anzahl von Seiten.
React-Nutzer kennen vielleicht Gatsby, den statischen Export von Next.js oder Navi. Mit all diesen Tools lassen sich Seiten ganz einfach aus Komponenten erstellen. Statisches Rendering und Prerendering funktionieren jedoch unterschiedlich: Statisch gerenderte Seiten sind interaktiv, ohne dass viel clientseitiges JavaScript ausgeführt werden muss. Prerendering hingegen verbessert die FCP einer Single-Page-Anwendung, die auf dem Client gestartet werden muss, damit die Seiten wirklich interaktiv sind.
Wenn Sie nicht sicher sind, ob bei einer bestimmten Lösung statisches Rendering oder Pre-Rendering verwendet wird, deaktivieren Sie JavaScript und laden Sie die Seite, die Sie testen möchten. Bei statisch gerenderten Seiten sind die meisten interaktiven Funktionen auch ohne JavaScript verfügbar. Vorab gerenderte Seiten können noch einige grundlegende Funktionen wie Links mit deaktiviertem JavaScript haben, der Großteil der Seite ist jedoch inaktiv.
Ein weiterer nützlicher Test ist die Netzwerkdrosselung in den Chrome-Entwicklertools. So können Sie sehen, wie viel JavaScript heruntergeladen wird, bevor eine Seite interaktiv wird. Für das Prerendering ist im Allgemeinen mehr JavaScript erforderlich, um interaktiv zu werden. Dieses JavaScript ist in der Regel komplexer als der Ansatz der progressiven Verbesserung, der beim statischen Rendering verwendet wird.
Serverseitiges Rendering im Vergleich zum statischen Rendering
Das serverseitige Rendering ist nicht für alle Anwendungen die beste Lösung, da es aufgrund seiner dynamischen Natur erhebliche Rechenressourcen beanspruchen kann. Viele serverseitige Rendering-Lösungen führen kein frühzeitiges Flush aus, verzögern den TTFB oder verdoppeln die gesendeten Daten (z. B. Inline-Zustände, die von JavaScript auf dem Client verwendet werden). In React kann renderToString()
langsam sein, da es synchron und einzelläufig ist.
Neuere React-Server-DOM-APIs unterstützen das Streaming, wodurch der erste Teil einer HTML-Antwort früher an den Browser gesendet werden kann, während der Rest noch auf dem Server generiert wird.
Um das serverseitige Rendering richtig einzurichten, müssen Sie unter Umständen eine Lösung für das Komponenten-Caching finden oder entwickeln, die Speichernutzung verwalten, Memorisierungstechniken verwenden und andere Probleme beheben. Sie verarbeiten oder erstellen dieselbe App häufig zweimal, einmal auf dem Client und einmal auf dem Server. Wenn Inhalte früher durch serverseitiges Rendering angezeigt werden, bedeutet das nicht unbedingt weniger Arbeit. Wenn auf dem Client viel Arbeit anfällt, nachdem eine servergenerierte HTML-Antwort eingegangen ist, kann dies dennoch zu höheren TBT- und INP-Werten für Ihre Website führen.
Beim serverseitigen Rendering wird HTML auf Anfrage für jede URL generiert. Das kann jedoch langsamer sein als das Bereitstellen statischer gerenderter Inhalte. Wenn Sie die zusätzlichen Vorkehrungen treffen, können Sie mit serverseitigem Rendering und HTML-Caching die Server-Renderingzeit erheblich reduzieren. Der Vorteil des serverseitigen Renderings besteht darin, dass mehr „Live“-Daten abgerufen und auf eine größere Anzahl von Anfragen reagiert werden kann als beim statischen Rendering. Seiten, die personalisiert werden müssen, sind ein konkretes Beispiel für Anfragen, die mit dem statischen Rendering nicht gut funktionieren.
Beim Erstellen einer PWA kann das serverseitige Rendering auch interessante Entscheidungen erfordern: Ist es besser, das Service Worker-Caching für die gesamte Seite zu verwenden oder einzelne Inhalte einfach serverseitig zu rendern?
Clientseitiges Rendering
Beim clientseitigen Rendering werden Seiten direkt im Browser mit JavaScript gerendert. Die gesamte Logik, das Abrufen von Daten, das Erstellen von Vorlagen und das Routing werden auf dem Client und nicht auf dem Server ausgeführt. Das Ergebnis ist, dass mehr Daten vom Server an das Gerät des Nutzers übergeben werden. Das hat wiederum seine eigenen Vor- und Nachteile.
Das clientseitige Rendering kann für Mobilgeräte schwierig zu implementieren und zu beschleunigen sein.
Mit ein wenig Aufwand, um ein knappes JavaScript-Budget einzuhalten und mit möglichst wenigen Rückgaben nützliche Informationen zu liefern, können Sie mit dem clientseitigen Rendering fast die Leistung des reinen serverseitigen Renderings erzielen. Sie können den Parser schneller nutzen, indem Sie wichtige Scripts und Daten mit <link rel=preload>
senden. Wir empfehlen auch, Muster wie PRPL zu verwenden, damit die erste und nachfolgende Navigation sofort erfolgt.
Der Hauptnachteil des clientseitigen Renderings besteht darin, dass die erforderliche Menge an JavaScript mit zunehmender Größe einer Anwendung tendenziell zunimmt, was sich auf die INP einer Seite auswirken kann. Das wird besonders schwierig, wenn neue JavaScript-Bibliotheken, Polyfills und Drittanbietercode hinzugefügt werden, die um die Verarbeitungsleistung konkurrieren und oft verarbeitet werden müssen, bevor die Inhalte einer Seite gerendert werden können.
Bei Anwendungen, die clientseitiges Rendering verwenden und auf große JavaScript-Bundles angewiesen sind, sollten Sie aggressives Code-Splitting in Betracht ziehen, um die TBT und INP beim Seitenaufbau zu senken. Außerdem sollten Sie JavaScript per Lazy Loading ausliefern, damit nur das bereitgestellt wird, was der Nutzer benötigt und wann er es benötigt. Bei Websites mit wenig oder keiner Interaktivität kann das serverseitige Rendering eine skalierbarere Lösung für diese Probleme darstellen.
Wenn Sie Single-Page-Anwendungen entwickeln, können Sie die Kernteile der Benutzeroberfläche, die von den meisten Seiten gemeinsam genutzt werden, identifizieren und das Application Shell Caching anwenden. In Kombination mit Service Workern kann dies die wahrgenommene Leistung bei wiederholten Besuchen erheblich verbessern, da die Seite ihre Anwendungs-Shell-HTML und Abhängigkeiten von CacheStorage
sehr schnell laden kann.
Bei der Rehydration werden serverseitiges und clientseitiges Rendering kombiniert.
Rehydration ist ein Ansatz, mit dem die Nachteile von clientseitigem und serverseitigem Rendering ausgeglichen werden sollen, indem beides verwendet wird. Navigationsanfragen wie das Laden oder Aktualisieren der gesamten Seite werden von einem Server verarbeitet, der die Anwendung in HTML rendert. Anschließend werden das JavaScript und die für das Rendering verwendeten Daten in das resultierende Dokument eingebettet. Wenn dies sorgfältig durchgeführt wird, wird ein schnelles FCP wie beim serverseitigen Rendern erreicht, das dann durch erneutes Rendern auf dem Client fortgesetzt wird. Dies ist eine effektive Lösung, kann aber erhebliche Leistungseinbußen mit sich bringen.
Der Hauptnachteil des serverseitigen Renderings mit Rehydration besteht darin, dass sich das Verfahren auch dann negativ auf die TBT und die INP auswirken kann, wenn es die FCP verbessert. Serverseitig gerenderte Seiten können zwar geladen und interaktiv erscheinen, sie können jedoch erst dann auf Eingaben reagieren, wenn die clientseitigen Scripts für Komponenten ausgeführt und Event-Handler angehängt wurden. Auf Mobilgeräten kann das mehrere Minuten dauern, was Nutzer verwirren und frustrieren kann.
Ein Problem mit der Reaktivierung: Eine App zum Preis von zwei
Damit das clientseitige JavaScript genau dort weitermacht, wo der Server aufgehört hat, ohne alle Daten noch einmal anzufordern, mit denen der Server seine HTML-Seite gerendert hat, serialisieren die meisten serverseitigen Rendering-Lösungen die Antwort aus den Datenabhängigkeiten einer Benutzeroberfläche als Script-Tags im Dokument. Da dadurch viel HTML dupliziert wird, kann die Rehydration mehr Probleme als nur eine verzögerte Interaktivität verursachen.
Der Server gibt in einer Navigationsanfrage eine Beschreibung der Benutzeroberfläche der Anwendung zurück, aber auch die Quelldaten, die zum Erstellen dieser Benutzeroberfläche verwendet wurden, und eine vollständige Kopie der Implementierung der Benutzeroberfläche, die dann auf dem Client gestartet wird. Die Benutzeroberfläche wird erst interaktiv, wenn bundle.js
geladen und ausgeführt wurde.
Die Leistungsmesswerte, die von echten Websites mit serverseitigem Rendering und Rehydration erfasst wurden, zeigen, dass dies selten die beste Option ist. Der wichtigste Grund ist die Auswirkung auf die Nutzerfreundlichkeit, wenn eine Seite zwar fertig aussieht, aber keine ihrer interaktiven Funktionen funktioniert.
Es gibt jedoch Hoffnung für das serverseitige Rendering mit Rehydration. Wenn Sie das serverseitige Rendering nur für Inhalte verwenden, die sich gut im Cache speichern lassen, lässt sich die TTFB kurzfristig reduzieren. Das Ergebnis ist ähnlich wie beim Pre-Rendering. Eine inkrementelle, progressive oder teilweise Rehydrierung könnte der Schlüssel sein, um diese Methode in Zukunft praktikabler zu machen.
Serverseitiges Rendering streamen und nach und nach rehydrieren
Das serverseitige Rendering hat sich in den letzten Jahren stark weiterentwickelt.
Beim Streaming-serverseitigen Rendering können Sie HTML in Chunks senden, die der Browser nach und nach rendern kann, sobald sie empfangen werden. So kann das Markup schneller an Ihre Nutzer gesendet werden, was die FCP beschleunigt. In React bedeutet die asynchrone Ausführung von Streams in renderToPipeableStream()
im Vergleich zur synchronen Ausführung in renderToString()
, dass der Backpressure gut verarbeitet wird.
Auch die progressive Rehydration ist eine Überlegung wert. In React ist sie implementiert. Bei diesem Ansatz werden einzelne Teile einer servergerenderten Anwendung im Laufe der Zeit „gebootet“, anstatt die gesamte Anwendung wie bisher üblich auf einmal zu initialisieren. So lässt sich die Menge an JavaScript reduzieren, die zum Interaktivisieren von Seiten erforderlich ist. Sie können das clientseitige Upgrade von Seitenteilen mit niedriger Priorität verschieben, um zu verhindern, dass der Haupt-Thread blockiert wird. So können Nutzerinteraktionen schneller nach der Nutzerinitiierung erfolgen.
Mit der progressiven Rehydration können Sie auch eine der häufigsten Fallstricke beim serverseitigen Rendering vermeiden: Ein serverseitig gerenderter DOM-Baum wird zerstört und dann sofort wieder aufgebaut. Das liegt meistens daran, dass für das anfängliche synchrone clientseitige Rendering Daten erforderlich waren, die noch nicht ganz fertig waren, oft eine noch nicht aufgelöste Promise
.
Teilweise Rehydration
Die teilweise Rehydrierung hat sich als schwierig zu implementieren erwiesen. Dieser Ansatz ist eine Erweiterung der progressiven Rehydration, bei der einzelne Teile der Seite (Komponenten, Ansichten oder Bäume) analysiert und die Teile mit wenig Interaktivität oder ohne Reaktivität identifiziert werden. Für jeden dieser größtenteils statischen Teile wird der entsprechende JavaScript-Code dann in inerte Referenzen und dekorative Funktionen umgewandelt, wodurch der clientseitige Footprint auf nahezu Null reduziert wird.
Die teilweise Hydratisierung birgt jedoch eigene Probleme und Kompromisse. Das stellt einige interessante Herausforderungen für das Caching dar. Außerdem können wir bei der clientseitigen Navigation nicht davon ausgehen, dass serverseitig gerenderte HTML-Seiten für inaktive Teile der Anwendung ohne vollständiges Laden der Seite verfügbar sind.
Trisomorphes Rendering
Wenn Dienstworker für Sie infrage kommen, sollten Sie das trisomorphe Rendering in Betracht ziehen. Mit dieser Methode können Sie das serverseitige Streaming-Rendering für die erste Navigation oder Navigationen ohne JavaScript verwenden und dann den Service Worker für das Rendering von HTML für Navigationen übernehmen lassen, nachdem er installiert wurde. So können zwischengespeicherte Komponenten und Vorlagen auf dem neuesten Stand gehalten und SPA-Navigationen zum Rendern neuer Ansichten in derselben Sitzung aktiviert werden. Dieser Ansatz funktioniert am besten, wenn Sie denselben templating- und Routing-Code für den Server, die Clientseite und den Service Worker verwenden können.
SEO-Aspekte
Bei der Auswahl einer Web-Rendering-Strategie berücksichtigen Teams oft die Auswirkungen auf die Suchmaschinenoptimierung. Das serverseitige Rendering ist eine beliebte Option, um eine „vollständige“ Website zu präsentieren, die von Crawlern interpretiert werden kann. Crawler können JavaScript verstehen, aber es gibt oft Einschränkungen beim Rendern. Das clientseitige Rendering kann funktionieren, erfordert aber oft zusätzliche Tests und Overhead. In letzter Zeit ist auch das dynamische Rendering eine Option, die Sie in Betracht ziehen sollten, wenn Ihre Architektur stark auf clientseitiges JavaScript angewiesen ist.
Im Zweifelsfall können Sie mit dem Tool zum Test auf Optimierung für Mobilgeräte prüfen, ob der von Ihnen gewählte Ansatz die gewünschten Ergebnisse liefert. Sie sehen eine visuelle Vorschau, wie eine Seite für den Crawler von Google erscheint, den serialisierten HTML-Inhalt, der nach der Ausführung von JavaScript gefunden wird, und alle beim Rendern auftretenden Fehler.
Fazit
Wenn Sie sich für einen Ansatz zum Rendern entscheiden, sollten Sie Ihre Engpässe messen und verstehen. Überlegen Sie, ob Sie mit statischem Rendering oder serverseitigem Rendering schon fast am Ziel sind. Es ist in Ordnung, hauptsächlich HTML mit minimalem JavaScript zu senden, um eine interaktive Umgebung zu schaffen. Hier ist eine praktische Infografik, die das Server-Client-Spektrum zeigt:
Gutschriften
Vielen Dank an alle für ihre Rezensionen und Inspiration:
Jeffrey Posnick, Houssein Djirdeh, Shubhie Panicker, Chris Harrelson und Sebastian Markbåge