18.05.2022

Serverseitiges Rendering mit JavaScript

Mit der Implementation von Server Side Rendering (SSR) werden die besten Aspekte althergebrachter Methoden, in die Welt moderner, dynamischer Webapplikationen integriert. In SSR zu investieren ist immer dann sinnvoll, wenn auf Punkte wie Performance, Cachebarkeit und SEO-Qualität hohen Wert gelegt wird. Mit SSR steigt aber auch die Komplexität und es muss gut abgewogen werden, ob sich der Einsatz lohnt oder nicht.

Was ist SSR?

Was ist Server Side Rendering (SSR)? Einfach ausgedrückt: der Inhalt einer Website oder Webapplikation wird auf dem Server und nicht auf dem Client-Computer gerendert. Früher, bevor mit dem heute verbreiteten Client Side Rendering Libraries und Frameworks wie React, Angular und Vue zur etablierten Norm wurden, war es Standard, ausschliesslich SSR zu machen. Mit der Entwicklung des Web 2.0 wurden Webauftritte interaktiver, wodurch das Rendering mehr und mehr vom Server in den Browser (den Client) verschoben wurde. Mit den vielen Möglichkeiten, die Client Side Rendering bietet, gehen aber Nachteile einher, wie zum Beispiel:

  • Da der Server keinen Inhalt mehr ausliefert, sondern dynamisch nachlädt, kann es potenziell auch für einen Suchmaschinen-Crawler schwer sein, den Inhalt einer Webseite sauber zu indexieren.
  • Bevor eine Seite komplett dargestellt werden kann, muss der Browser zunächst viele Berechnungen durchführen. Das kann die Geschwindigkeit der Seite verlangsamen, und vor allem Benutzer:innen mit älteren und weniger leistungsfähigen Geräten oder Mobile-Devices kann die User Experience dadurch deutlich schlechter werden.
  • Berechnungen, welche im Browser der Benutzer:innen gemacht werden müssen, können nicht in einen Cache geschrieben werden können. Das führt dazu, dass die Berechnungen für jedes Gerät neu ausgeführt werden müssen.

Hier kommt nun SSR ins Spiel: Das Ziel von SSR ist, den Benutzer:innen schon initial vom Server alle Inhalte fertig gerendert auszugeben, und über den Browser nur noch die Interaktivität mit dem Nutzer zur Verfügung zu stellen.

Wie funktioniert die Implementation von SSR?

Wir setzen unseren Fokus für SSR in eine NodeJS-Applikation mit Einsatz von React. Auch andere moderne JavaScript-Frameworks bieten einfache Unterstützung für SSR. Die hier geschilderten Erkenntnisse und Herausforderungen werden in ähnlicher Form wohl auch beim Einsatz alternativer Technologien anzutreffen sein.

Da sowohl im Browser wie auch auf dem Server mit der gleichen Programmiersprache gearbeitet wird (JavaScript), haben wir relativ einfach die Möglichkeit, den gleichen Code, welcher im Browser verwendet wird, auch auf dem Server auszuführen. 

Code kann in einigen wenigen Schritten implementiert werden:

  • Der Frontend-Rendering-Code wird beim Aufrufen einer Seite im Server gerendert. Hier kann genau der gleiche Einstiegspunkt in z.B. die oberste React-Komponente wie im Browser verwendet werden, mit dem Unterschied, dass ReactDOM mit ReactDOMServer ausgetauscht wird. Zusätzlich müssen beim Einsatz von Komponenten wie dem react-router alternative Komponenten eingesetzt werden, wenn diese im Server aufgerufen werden. Der Server selbst kennt weder Browser-History noch eine momentane URL und diese müssen entsprechend übergeben werden, wo notwendig.
  • Im Browser wird der Einstieg in die React-Komponente von einem Aufruf an die ReactDOM.render-Funktion mit einer ReactDOM.hydrate-Funktion ersetzt. Das führt dazu, dass React weiss, dass bereits ein vorgerendertes Markup vorhanden ist.
  • Der Initiale State, welcher beim Rendering im Server verwendet wird, wird als JSON-Objekt an den Browser mitgegeben, sodass die Applikation im Browser weiss, was für Daten verwendet wurden und diese nicht selbst nachladen muss.

Auch wenn ein kleinerer Proof-of-concept sicherlich schnell und unkompliziert auf die Beine gestellt ist, gibt es dennoch einige Herausforderungen, die beim eigentlichen Einsatz auftauchen können.

Herausforderungen und Erkenntnisse

Einige der Probleme, auf welche wir bei zahlreichen Projekten gestossen sind, möchten wir hier gerne aufzeigen. Diese Liste ist aber nicht abschliessend, denn jedes Projekt birgt seine eigenen Herausforderungen.

Erkenntnis 1: Transpilierung und Paketierung der Server-Software haben Konsequenzen

Oft wird NodeJS-Software aufgerufen, ohne dass sie durch eine Transpilierungs-Software wie Babel oder TypeScript gelaufen ist. Sobald wir anfangen, Browser-Technologien wie ES6-Module und JSX zu verwenden, wird dies auch für den Server notwendig. Dies kann Konsequenzen für Build- und Deployment-Prozesse haben oder Einfluss auf den Code haben. Ein Learning ist: sobald Paketierung ins Spiel kommt, ist ein dynamisches require() nicht mehr möglich.

Erkenntnis 2: Zugriffe auf window oder document brechen im SSR

Da im Server die globalen Objekte  window und document nicht verfügbar sind, muss beachtet werden, dass diese im Frontend-Code, welcher auch im Backend ausgeführt werden soll, verwendet werden. Notwendige Objekte müssen explizit vom SSR ausgeschlossen werden.

Erkenntnis 3: Nicht alle third-party-Pakete unterstützen es, auf dem Server aufgerufen zu werden

Bei der Verwendung von third-party-Paketen muss darauf geachtet werden, dass diese das Ausführen im Browser und im Server unterstützen. Nicht alle Pakete, welche im Browser laufen, können auch im Server ausgeführt werden. Dies schränkt potenziell die Auswahl an Paketen ein.

Erkenntnis 4: Nicht alle Browser-Funktionen existieren 1:1 in der NodeJS-Umgebung

Browserfunktionen wie fetch, XMLHttpRequests existieren nicht oder nicht genau gleich im NodeJS-Environment. Das bedeutet, dass bei der Verwendung dieser Funktionen besonders darauf geachtet werden muss, ob und wie sie eingesetzt werden.

Erkenntnis 5: Unsauberes Verwenden von Libraries wie React / Redux kann Probleme verursachen

Es muss besonders darauf geachtet werden, dass die Funktionalitäten von React-Komponenten und Redux-Stores sauber verwendet werden. Eine unsaubere Verwendung kann beim initialen Rendering dazu führen, dass das Server-Rendering nicht vollständig ist oder dass beim Übergeben des Status vom Server zum Browser die Serialisierung nicht wie gewünscht funktioniert.

Erkenntnis 6: Das Laden von Daten ist nicht immer trivial 

Daten, die für ein initiales Rendering notwendig sind, müssen in den jeweiligen Ansichten im Backend geladen und dem Rendering übergeben werden. Das kann je nach Aufbau des Daten-Ladevorgangs im Frontend eine Herausforderung sein. 

Fazit

SSR kann Probleme mit Performance, Cachebarkeit und SEO-Qualität lösen, kann aber auch Herausforderungen bergen. In SSR zu investieren ist vor allem dann empfehlenswert, wenn auf die genannten Punkte viel Wert gelegt wird. 

Applikationen, welche viel statischen Content beinhalten, können am meisten von SSR profitieren, aber auch Applikationen, die viel benutzerspezifischen dynamischen Content enthalten, erfahren einen Performance-Vorteil, da das Grundgerüst der Seite gecached werden kann und die Applikation dadurch für Benutzer:innen spürbar schneller wird.

Viele der Herausforderungen sind am spürbarsten, wenn SSR in eine bestehende Applikation mit Frontend-Rendering eingebaut wird. Wenn aber eine Applikation von Anfang an unter Berücksichtigung von SSR aufgebaut wird, können Vorteile genutzt und die User Experience verbessert werden. 

 

 

Über den Autor: Alexander Rothmund, Application Engineer