Eine der wichtigsten Entscheidungen, die Webentwickler treffen müssen, besteht darin, wo Logik und Rendering in ihrer Anwendung implementiert werden sollen. Das kann schwierig sein, weil es so viele Möglichkeiten gibt, eine Website zu erstellen.
Unser Verständnis für diesen Bereich beruht auf unserer Bemühungen, in den letzten Jahren mit großen Websites in Chrome zu kommunizieren. Generell empfehlen wir Entwicklern, statt einer vollständigen Wiederherstellung ein serverseitiges oder statisches Rendering in Betracht zu ziehen.
Um die Architekturen, aus denen wir uns bei dieser Entscheidung wählen, besser zu verstehen, benötigen wir ein solides Verständnis jedes Ansatzes und eine einheitliche Terminologie, die wir verwenden sollten, wenn wir über sie sprechen. Die Unterschiede zwischen den Rendering-Ansätzen verdeutlichen die Vor- und Nachteile des Renderings im Web hinsichtlich der Seitenleistung.
Terminologie
Rendering
- Serverseitiges Rendering (SSR)
- Clientseitige oder universelle App als HTML auf dem Server rendern
- Clientseitiges Rendering (CSR)
- Eine Anwendung in einem Browser rendern und das DOM mit JavaScript ändern
- Rehydrierung
- Die JavaScript-Ansichten des Clients werden gestartet, damit der DOM-Baum und die Daten des vom Server gerenderten HTML-Codes wiederverwendet werden.
- Pre-Rendering
- Clientseitige Anwendung zum Build-Zeitpunkt ausführen, um ihren Anfangszustand als statischen HTML-Code zu erfassen
Leistung
- Time to First Byte (TTFB)
- Die Zeit zwischen dem Klicken auf einen Link und dem ersten Byte des Inhalts, der auf der neuen Seite geladen wird.
- First Contentful Paint (FCP)
- Die Zeit, zu der angeforderter Inhalt (Artikeltext usw.) sichtbar wird.
- Interaction to Next Paint (INP)
- Ein repräsentativer Messwert, der beurteilt, ob eine Seite konsistent schnell auf Nutzereingaben reagiert.
- Total Blocking Time (TBT)
- Ein Proxy-Messwert für INP, der berechnet, wie lange der Hauptthread beim Seitenaufbau blockiert wurde.
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 für das Abrufen und Erstellen von Vorlagen im Client vermieden, da diese vom Renderer verarbeitet werden, bevor der Browser eine Antwort erhält.
Das serverseitige Rendering erzeugt in der Regel ein schnelles FCP. Wenn du die Seitenlogik und das Rendering auf dem Server ausführst, musst du nicht zu viel JavaScript an den Client senden. Dadurch wird das TBT einer Seite reduziert, was auch zu einem 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 eröffnet interessante Browseroptimierungen wie das Parsen 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 Ihre eigenen Kosten für JavaScript reduzieren, um mehr Budget für den Rest zu erhalten. Bei diesem Ansatz gibt es jedoch einen möglichen Nachteil: Das Generieren von Seiten auf dem Server nimmt Zeit in Anspruch, was die TTFB Ihrer Seite erhöhen kann.
Ob das serverseitige Rendering für Ihre Anwendung ausreicht, hängt vor allem von der Art der zu erstellenden Umgebung ab. Es wird schon lange diskutiert, ob serverseitiges und clientseitiges Rendering richtig eingesetzt werden sollen. Für einige Seiten kannst du aber auch serverseitiges Rendering verwenden. Auf einigen Websites wurden erfolgreich Hybrid-Rendering-Verfahren eingeführt. Der Server von Netflix rendert beispielsweise seine relativ statischen Landingpages, während das JS für interaktionsintensive Seiten vorabgerufen wird. Dadurch erhöht sich die Wahrscheinlichkeit, dass diese schwereren, clientseitig gerenderten Seiten schnell geladen werden.
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 DOM APIs auf Server oder darauf basierende Lösungen wie Next.js für das serverseitige Rendering verwenden. Vue-Nutzer können den Leitfaden zum serverseitigen Rendering oder Nuxt von Vue verwenden. Angular hat Universal. Die meisten gängigen Lösungen verwenden jedoch eine Form der Hydration. Seien Sie sich also der Methode bewusst, die Ihr Tool anwendet.
Statisches Rendering
Das statische Rendering erfolgt zum Build-Zeitpunkt. Dieser Ansatz bietet einen schnellen FCP-Wert sowie eine geringere TBT und INP, solange Sie die Menge des clientseitigen JS auf Ihren Seiten begrenzen. Anders als beim serverseitigen Rendering wird auch eine konsistent schnelle TTFB erreicht, 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 im Voraus generierten HTML-Antworten können Sie statische Renderings für mehrere CDNs bereitstellen und von Edge-Caching profitieren.
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 Anwendung dynamisch und nicht als Build-Schritt generiert wird. Tools zum Generieren statischer Websites wie 11ty, Jekyll und Metalsmith sind auf ihren statischen Charakter ausgelegt und bieten einen eher vorlagenorientierten 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 Websites eine große Anzahl eindeutiger Seiten haben.
React-Nutzer sind vielleicht mit Gatsby, dem statischen Next.js-Export oder Navi vertraut, mit denen sich Seiten aus Komponenten ganz einfach erstellen lassen. Statisches und Pre-Rendering verhalten sich jedoch unterschiedlich: statisch gerenderte Seiten sind interaktiv, ohne dass viel clientseitiges JavaScript ausgeführt werden muss. Beim Pre-Rendering wird der FCP-Wert einer Single-Page-Anwendung verbessert, die auf dem Client gestartet werden muss, damit Seiten wirklich interaktiv werden.
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 zu testende Seite. Bei statisch gerenderten Seiten gibt es die meisten interaktiven Funktionen auch ohne JavaScript. Vorab gerenderte Seiten haben möglicherweise noch einige grundlegende Funktionen wie Links mit deaktiviertem JavaScript, aber der Großteil der Seite ist inaktiv.
Ein weiterer nützlicher Test ist die Netzwerkdrosselung in den Chrome-Entwicklertools, um festzustellen, wie viel JavaScript heruntergeladen wird, bevor eine Seite interaktiv wird. Beim Pre-Rendering ist im Allgemeinen mehr JavaScript erforderlich, damit es interaktiv wird. Außerdem ist JavaScript tendenziell komplexer als beim statischen Rendering (Progressive Enhancement).
Serverseitiges und statisches Rendering im Vergleich
Serverseitiges Rendering ist nicht für alles die beste Lösung, da es dynamisch ist und hohe Kosten für die Rechenleistung verursachen kann. Bei vielen serverseitigen Rendering-Lösungen wird nicht vorzeitig geleert, TTFB verzögert oder die gesendeten Daten verdoppelt (z. B. Inline-Zustände, die von JavaScript auf dem Client verwendet werden). In React kann renderToString()
langsam sein, da es synchron und Singlethread ist.
Neuere React-Server-DOM APIs unterstützen Streaming, bei dem der erste Teil einer HTML-Antwort schneller an den Browser gesendet wird, während der Rest noch auf dem Server generiert wird.
Damit das serverseitige Rendering „richtig“ ist, kann unter anderem eine Lösung für das Komponenten-Caching gefunden oder erstellt, der Arbeitsspeicherverbrauch verwaltet und Methoden zur Memoisierung verwendet werden. Häufig wird dieselbe Anwendung zweimal verarbeitet oder neu erstellt, einmal auf dem Client und einmal auf dem Server. Das serverseitige Rendering, bei dem Inhalte früher angezeigt werden, bedeutet nicht unbedingt, weniger Arbeit zu erledigen. Wenn Sie auf dem Client viel Arbeit haben, nachdem eine servergenerierte HTML-Antwort auf dem Client eingeht, 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, allerdings kann es länger dauern als nur statische gerenderte 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 besteht in der 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 Anfragen, die mit statischem Rendering nicht gut funktionieren.
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 das Routing werden auf dem Client und nicht auf dem Server verarbeitet. Das effektive Ergebnis ist, dass mehr Daten vom Server an das Gerät des Nutzers übergeben werden, was mit eigenen Kompromissen verbunden ist.
Clientseitiges Rendering kann für Mobilgeräte schwierig sein und schnell bleiben.
Mit ein wenig Arbeit, ein eng gefasstes JavaScript-Budget einzuhalten und einen Mehrwert in möglichst wenigen Umläufen zu liefern, können Sie clientseitiges Rendering erhalten, um die Leistung eines reinen serverseitigen Renderings fast zu replizieren. Damit der Parser schneller arbeitet, können Sie wichtige Skripts und Daten mit <link rel=preload>
bereitstellen. Außerdem empfehlen wir die Verwendung von Mustern wie PRPL, damit die erste und nachfolgende Navigation sofort anfängt.
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 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 Umgebungen, die clientseitiges Rendering nutzen und auf große JavaScript-Bundles angewiesen sind, sollten Sie eine aggressive Codeaufteilung in Betracht ziehen, um TBT und INP beim Seitenaufbau zu senken, sowie Lazy Loading von JavaScript, damit nur das bereitzustellen, was der Nutzer braucht, wenn es benötigt wird. Für Umgebungen 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 die Kernbestandteile der Benutzeroberfläche identifizieren möchten, die von den meisten Seiten genutzt werden, können Sie die Anwendungsshell-Caching-Methode anwenden. In Kombination mit Service Workern kann dies die wahrgenommene Leistung bei wiederholten Besuchen erheblich verbessern, da die Seite die Anwendungsshell-HTML und die Abhängigkeiten von CacheStorage
sehr schnell laden kann.
Bei der Rehydrierung werden server- und clientseitiges Rendering kombiniert.
Mit der Rehydrierung wird versucht, die Kompromisse zwischen clientseitigem und serverseitigem Rendering durch beides auszugleichen. 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 Rendering verwendeten Daten in das resultierende Dokument eingebettet. Bei sorgfältiger Vorgehensweise wird ein schnelles FCP wie beim serverseitigen Rendering erreicht, das dann durch erneutes Rendering auf dem Client „aufnimmt“. Dies ist eine effektive Lösung, kann aber erhebliche Leistungsnachteile haben.
Der größte Nachteil des serverseitigen Renderings mit Rehydrierung besteht darin, dass es erhebliche negative Auswirkungen auf TBT und INP haben kann, auch wenn es FCP verbessert. Serverseitig gerenderte Seiten können dem Anschein nach geladen und interaktiv sein. Sie können jedoch erst auf Eingaben reagieren, wenn die clientseitigen Skripts für die Komponenten ausgeführt und Event-Handler angehängt wurden. Auf Mobilgeräten kann dies Minuten dauern, verwirrend und frustrierend für den Nutzer.
Ein Problem mit der Rehydrierung: eine App zum Preis von zwei
Damit das clientseitige JavaScript dort weitermachen kann, wo der Server aufgehört hat, ohne alle Daten, mit denen der Server seinen HTML-Code gerendert hat, noch einmal anzufordern, werden in den meisten serverseitigen Rendering-Lösungen die Antworten der Datenabhängigkeiten einer UI als Skript-Tags im Dokument archiviert. Da dabei viel HTML dupliziert wird, kann die Rehydrierung mehr Probleme verursachen als nur verzögerte Interaktivität.
Der Server gibt 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 im Client gestartet wird. Die Benutzeroberfläche wird erst interaktiv, nachdem bundle.js
das Laden und Ausführen abgeschlossen hat.
Leistungsmesswerte, die von echten Websites durch serverseitiges Rendering und Rehydrierung erfasst werden, weisen darauf hin, dass dies selten die beste Option ist. Der wichtigste Grund ist die Auswirkung auf die Nutzererfahrung, wenn eine Seite bereit aussieht, aber keine der interaktiven Funktionen funktioniert.
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 inkrementelle, progressive oder teilweise Rehydrierung kann der Schlüssel sein, um diese Methode in Zukunft rentabler zu machen.
Serverseitiges Rendering streamen und schrittweise rehydrieren
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. Dadurch können Ihre Nutzer Markups schneller abrufen und der FCP-Wert wird beschleunigt. Wenn Streams in renderToPipeableStream()
im Vergleich zum synchronen renderToString()
asynchron sind, bedeutet das, dass ein Rückdruck gut verarbeitet wird.
Die progressive Rehydrierung ist ebenfalls eine gute Idee und React hat sie implementiert. 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. Dadurch lässt sich die Menge an JavaScript reduzieren, die zur Interaktion mit Seiten erforderlich ist, da du das clientseitige Upgrade von Teilen der Seite mit niedriger Priorität aufschieben kannst, um zu verhindern, dass der Hauptthread blockiert wird. Nutzerinteraktionen können so früher erfolgen, nachdem der Nutzer sie initiiert hat.
Die progressive Wiederherstellung kann Ihnen auch dabei helfen, eines der häufigsten Fallstricke beim serverseitigen Rendering zu vermeiden: Ein vom Server gerenderter DOM-Baum wird gelöscht und dann sofort neu erstellt. Das ist häufig der Fall, weil für das erste synchrone clientseitige Rendering Daten erforderlich waren, die nicht ganz bereit waren, oft ein noch nicht aufgelöstes Promise
.
Teilweise Rehydrierung
Die teilweise Rehydrierung hat sich als schwierig erwiesen. Dieser Ansatz ist eine Erweiterung der progressiven Rehydrierung, bei der einzelne Teile der Seite (Komponenten, Ansichten oder Bäume) analysiert werden und die Teile mit wenig Interaktivität oder ohne Reaktivität identifiziert werden. Für jeden dieser zumeist statischen Teile wird der entsprechende JavaScript-Code dann in inerte Referenzen und dekorative Merkmale 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. Bei der clientseitigen Navigation können wir nicht davon ausgehen, 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 für Sie infrage kommen, ziehen Sie ein trisomorphes Rendering in Betracht. Mit dieser Technik können Sie serverseitiges Streaming für erste oder Nicht-JS-Navigationen verwenden und Ihren Service Worker das HTML-Rendering für Navigationen übernehmen lassen, nachdem es installiert wurde. Auf diese Weise 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.
Überlegungen zur SEO
Bei der Auswahl einer Web-Rendering-Strategie berücksichtigen Teams oft die Auswirkungen der SEO. Das serverseitige Rendering ist eine beliebte Wahl, um eine „vollständig aussehende“ Erfahrung zu bieten, die von Crawlern interpretiert werden kann. Crawler können JavaScript verstehen, es gibt jedoch oft Einschränkungen, wie sie gerendert werden. Clientseitiges Rendering kann funktionieren, erfordert aber oft zusätzliche Tests und zusätzlichen Aufwand. Seit Kurzem eignet sich auch das dynamische Rendering, wenn Ihre Architektur stark von clientseitigem JavaScript abhängt.
Im Zweifelsfall können Sie mit dem Tool zum Test auf Optimierung für Mobilgeräte ganz einfach testen, ob der von Ihnen gewählte Ansatz das erhofft. Sie zeigt eine visuelle Vorschau davon, wie eine Seite dem Google-Crawler angezeigt wird, den nach der Ausführung von JavaScript gefundenen seriellen HTML-Inhalt und alle Fehler, die während des Renderings auftreten.
Fazit
Wenn Sie sich für einen Renderingansatz entscheiden, sollten Sie Engpässe messen und verstehen. Überlegen Sie, ob Sie die meisten Schritte durch statisches oder serverseitiges Rendering erzielen können. Es ist in Ordnung, HTML meist mit minimalem JavaScript zu senden, um ein interaktives Erlebnis zu schaffen. Hier ist eine praktische Infografik, die das Server-Client-Spektrum veranschaulicht:
Guthaben
Vielen Dank an alle für ihre Rezensionen und die Inspiration:
Jeffrey Posnick, Houssein Djirdeh, Shubhie Panicker, Chris Harrelson und Sebastian Markbåge