Geschrieben von

Critical Rendering Path verstehen, analysieren und optimieren

WebDev

Critical Rendering Path verstehen

Um den Pagespeed im Allgemeinen, aber auch die Core Web Vitals einer Website zu optimieren, ist es essentiell zu verstehen wie Webseiten geladen werden. Vor allem der Teil zwischen Empfang der Ressourcen und Darstellung auf dem Screen ist dabei relevant. Genau dieser Teil wird als Critical Rendering Path bezeichnet. Konkreter sind damit die Schritte und Prozesse gemeint, die ein Browser durchführen muss, um HTML-, CSS- und JavaScript-Code auszuführen, verarbeiten und dann visuell für den Nutzer darzustellen. Grob gesagt sind dabei folgende Prozesse notwendig:

  1. Erstellung Document Object Model (DOM)
  2. Erstellung CSS Object Model (CSSOM)
  3. JavaScript ausführen
  4. Erstellung Render Tree
  5. Berechnung Layout
  6. Darstellung der Pixel auf dem Bildschirm (Paint)

Im Detail und visualisiert sieht das Ganze wie folgt aus (Erklärungen unter der Grafik):

  1. Der Nutzer landet auf der Seite und der Browser fordert über das Netzwerk alle Ressourcen an. Dazu gehören das HTML, CSS und JavaScript. Es werden auch Bilder und andere Elemente angefordert, diese blocken jedoch nicht das initiale Rendering der Seite.
  2. Nun bekommt der Browser das HTML. Es wird analysiert und daraus das DOM erstellt.
  3. Als nächstes kommt das CSS, welches ebenfalls vom Browser verarbeitet und das CSSOM erstellt wird.
  4. Jetzt werden die einzelnen Knoten aus dem DOM und CSSOM kombiniert, um daraus den Render Tree (Renderbaum) zu erstellen. Beim Render Tree handelt es sich um eine Baumstruktur aller sichtbaren Knoten, die im Anschluss für das Rendering erforderlich sind.
  5. Nun kommt der Layoutprozess, wo die Abmessungen und die Positionen eines jeden einzelnen Elements berechnet werden.
  6. Als letzten Schritt – als Paint genannt – werden die Pixel auf dem Bildschrim des Nutzers dargestellt.

Nun zu den einzelnen Schritten.

Document Object Model (DOM)

Das DOM wird vom Browser erstellt, nachdem das HTML vom Server bereitgestellt wurde und repräsentiert die interne Darstellung des HTMLs durch den Browser. Der HTML-Code wird dabei in eine Baumstruktur umgewandelt und besteht aus Knoten. Jeder Knoten entspricht einem HTML-Element und besitzt Eigenschaften. Über diese Eigenschaften werden die Attribute, Inhalte und Positionen der HTML-Elemente beschrieben.

Folgende Schritte sind dabei notwendig, um die Seite zu verarbeiten:

  • Tokensierung
  • Lexing
  • Erstellung DOM

Zunächst werden die Characters wie <html>, <head>, etc. in Tokens ([StartTag: html], [StartTag: head], etc.) umgewandelt (= Tokensierung). Dieser Prozess wird vom sogenannten Tokenizer übernommen. Anhand der spitzen Klammern der HTML-Characters weiß der Tokenizer wann ein Token beginnt und endet. Auf dieser Basis kann eine sinnvolle Struktur erstellt werden.

Während der Tokenizer diesen Prozess durchläuft, wird gleichzeitig ein anderer Prozess durchgeführt, wo die Tokens in Nodes umgewandelt werden (= Lexing). Wenn alle Nodes stehen und die Beziehungen zueinander definiert worden sind, haben wir das Document Objekt Model (DOM). Visuell kann man sich das DOM wie folgt vorstellen:

Der Vorteil vom DOM ist, dass es stufenweise ausgegeben werden kann. Der Browser muss es nicht komplett rendern, um die ersten Elemente sichtbar darzustellen. Bei einer HTML-Seite mit Logo und Navigation im Header können diese sofort nach dem Rendern angezeigt werden, bevor überhaupt der Rest geladen werden kann. Damit der Browser nun weiß, wie die einzelnen Inhalte dargestellt werden müssen, braucht er das CSS Object Model (CSSOM).

CSS Object Model (CSSOM)

Kurz zum ersten Unterschied zwischen DOM und CSSOM:

  • Das DOM enthält den Inhalt der Seite.
  • Das CSSOM enthält die Stile zu den Inhalten der Seite.

Auch hier sind folgende Schritte notwendig, damit der Browser das CSSOM parallel zum DOM erstellen kann:

  • Tokensierung
  • Lexing
  • Erstellung CSSOM

Die CSS-Anweisungen wie…

body {font-size: 16px}

…und…

p {font-weight: bold}

…müssen zunächst in Tokens umgewandelt werden. Danach erfolgt die Umwandlung in Nodes, um daraus dann das CSSOM zu erstellen. Ein visuelles Beispiel eines CSSOM sieht wie folgt aus:

Hier sollte man beachten, dass dies nicht das komplete CSSOM ist. Jeder Browser hat Standard-Styles, die man “User Agent Styles” bezeichnet. Diese Styles sieht man immer dann, wenn man selbst kein CSS zur Verfügung stellt. Für Chrome bzw. Chromium kann man die User Agent Styles hier einsehen oder hier für WebKit (Safari). Wenn wir jedoch ein eigenes Style und CSS zur Verfügung stellen, dann überschreibt unser Style die Styles des User Agent Styles und das CSSOM zeigt auch nur die Styles, die wir überschreiben. Styles, die wir nicht mit unserem CSS überschreiben, werden im CSSOM nicht aufgeführt.

Der wichtige Unterschied zwischen DOM und CSSOM ist, dass die DOM-Konstruktion schrittweise erfolgt, während CSSOM nicht. Nehmen wir an der Browser rendert die ersten CSS-Teile, die folgende Anweisungen enthalten:

body {
  font-size: 12px
}

p {
  font-weight: normal
}

Mit diesen Anweisungen kann die Seite noch nicht sofort dargestellt werden, da weitere CSS-Anweisungen folgen könnten, die z.B.

p {font-weight: normal}

…durch…

p {font-weight: bold}

…ersetzen. Mit CSS ist es möglich, Styles neu zu definieren bzw. werden die Styles nachrangig abgearbeitet (das Prinzip des Cascading). Wir können also nicht wie beim HTML einzelne Bestandteile schon ausspielen und darstellen, da das CSS noch verändert werden kann. Der Browser blockiert hier das Rendering, bis er das komplette CSS bekommen hat. Deshalb ist CSS standardmäßig “Render-blocking” (wenn man beispielhaft das CSS einer Seite ausschaltet, versteht man auch wieso: Ohne CSS kann die Seite nicht richtig dargestellt werden).

Dementsprechend kann die Baumstruktur (Render Tree) ohne CSSOM nicht erstellt werden. Zudem kann CSS auch “Skript-blocking” sein, da JS-Dateien warten müssen bis das CSSOM erstellt wurde, um ausgeführt zu werden. Dazu gleich mehr.

JavaScript ausführen

Nun kommt noch JavaScript ins Spiel. In der ersten Grafik konnten wir sehen, dass die DOM- und CSSOM-Erstellung parallel abläuft:

Kommt nun JavaScript dazu, dann haben wir zwei Auswirkungen:

  • Sobald der Browser auf ein synchrones JavaScript im HTML stößt – ganz gleich ob es Inline JS, ein internes oder externes Skript ist – wird die DOM-Konstruktion blockiert.
  • JavaScript kann auch nicht fortgesetzt werden, bis das CSSOM verfügbar ist. Sprich: Wenn der Browser auf eine JS-Datei stößt, das CSSOM jedoch nicht nicht bereit steht, wird auch die Ausführung des JavaScripts blockiert bis das CSSOM fertig ist.

Hilfe leitet dabei das “async”-Attribut, welches die DOM-Konstruktion nicht blockiert.

Render Tree

Nachdem DOM und CSSOM bereitstehen, werden sie nun zu einem Renderbaum zusammengefasst. Der Renderbaum ist dann die Basis für das Layout eines jeden sichtbaren Elements und wird dann für das Paint verwendet. Für die Erstellung des Render Trees werden folgende Schritte durchgeführt:

  1. Der Browser beginnt am obersten Punkt des DOM und geht durch jeden sichtbaren Knoten. Dabei werden Knoten, die keinen Einfluss auf das gerenderte Ergebnis haben wie z.B. Skripte, Meta-Tags, etc. nicht miteinbezogen. Ebenso auf “display:none” gesetzte Elemente werden nicht berücksichtigt.
  2. Dann wird für jeden sichtbaren Knoten die entsprechende Regel aus dem CSSOM gesucht und darauf angewendet.
  3. Nun werden alle sichtbaren Knoten mit Inhalten und Stilen ausgegeben.

Wenn der Renderbaum steht, kann es zum Layout gehen.

Layout

Was nun noch fehlt sind genaue Position und Größe der Knoten auf dem Bildschirm des Nutzers. Dies wird nun in der Layout-Phase berechnet. Das Meta-Tag

<meta name="viewport" content="…">

spielt dabei eine wichtige Rolle. Dieses Tag im Head-Bereich sagt dem Browser, an welcher Größe sich die CSS-Spezifikationen halten sollen. Im Attribut “content” wird die Breite angegeben bzw. auch Eigenschaften wie initial-scale (Zoomgrad), user-scaleable (Zoomen sperren oder erlauben) oder minimum-scale/maximum-scale (Zoomgrad einschränken).

Gibt man im Attribut “content” die Breite “width=device-width” an, so richtet sich die Breite am Ausgabegerät. Wird nun die Breite über das CSS für den Body auf 100 % gesetzt und das Ausgabegerät ist 320 Pixel breit, ergibt das eine Body-Breite von 320 Pixel. Wenn man im Attribut “content” nichts angibt, dann zieht der Browser den Standard-Wert von 980 Pixel heran. Greifen wir dann auf die Seite mit unserem Smartphone mit einer Breite von 320 Pixel zu, müssen wir rein- bzw. rauszoomen, um den Text erfassen zu können. Damit es bei der Darstellung zu keinen Problemen kommt, sollte daher der Viewport stets angegeben werden.

Paint

Im letzten Schritt werden die Pixel auf Bildschirm dargestellt.

Critical Rendering Path analysieren

Es gibt verschiedene Möglichkeiten wie man den kritischen Rendering-Pfad und die einzelnen Prozesse davon analyiseren kann. Beginnen wir mit der Analyse der Zeit, die benötigt wird, um das HTML ins DOM zu konvertieren. In den Chrome Developer Tools können wir im Performance-Tab einen Pageload aufnehmen und dann unter “Call Tree” die Aktivität “Parse HTML” genauer anschauen:

Wie man sehen kann, braucht die analysierte Seite 17.9 Sekunden, um das HTML ins DOM zu konvertieren. Ebenso lässt sich die Zeit für die CSSOM-Erstellung, Layout und Paint analysieren:

Critical Rendering Path optimieren

Ziel ist es zwei Aspekte des Critical Rendering Paths zu optimieren:

  • Die Zeit, die der Webbrowser für alle diese Schritte benötigt, reduzieren.
  • Während des gesamten Prozesses, den Inhalte priorisiert zu laden, der aktuell für den Nutzer relvant ist.

Sehe dir die nachfolgenden Grafiken an, was ein optimierter Rendering-Pfad bedeutet. Zunächst sehen wir wie die Seite unoptimiert lädt. Der Nutzer muss lange warten bis was passiert:

Nun sieht man einen optimierten Rendering-Pfad wo Elemente nacheinander geladen werden und der Nutzer schon frühzeitig mit der Seite interagieren kann und das Gefühl hat, die Seite schein schneller zu laden:

Für die Optimierung des Critical Rendering Paths sind folgende Schritte notwendig:

  1. Bytes minimieren
  2. Kritische Ressourcen minimieren
  3. Critical Rendering Path Länge reduzieren
  4. Critical CSS und JavaScript auslagern

Bytes minimieren

Je weniger Bytes der Browser verarbeiten muss, desto schneller wird die Seite gerendert. Daher sollten HTML, CSS und JavaScript minimiert, komprimiert und gecacht werden. Zudem sollte man die Anzahl an CSS- und JavaScript-Dateien reduzieren. Nachfolgend gehe ich auf die wichtigsten Optimierungen ein.

  • Kommentare entfernen: HTML-, CSS- und JavaScript enthalten sehr oft Kommentare, die zwar für Entwickler hilfreich, aber für Browser unwichtig sind. Die Gesamtgröße der Seite kann reduziert werden, indem man diese Kommentare entfernt.
  • Leerzeichen: Ähnlich wie bei den Kommentaren, kann das Entfernen von Leerzeichen in HTML, CSS und JavaScript zu Einsparungen bei der Größe führen.
  • Nicht benötigen Code entfernen: Gibt es CSS-Klassen und -Anweisungen, die gar nicht greifen? Gibt es doppelte CSS-Deklarationen? Zum Beispiel könnte in der style.css .header {font-size: 12px} und weiter unten .header {width: 50%} stehen. Hier sollten wir die 2 zu einer Deklaration zusammenfassen, um ein paar Bytes einzusparen.
  • Komprimierung: Bei der Komprimierung werden Dateien wie HTML, CSS und JavaScript vom Server in abgespeckter Form an den Browser gesendet, der diese dann wiederum entpackt. Dadurch werden kleinere Dateien versendet, was zu einer besseren Ladegeschwindigkeit führt. Wie man GZIP-Komprimierung aktiviert, kann man hier nachlesen. Alternativ kann man auch Brotli einsetzen.

Die ersten 3 Methoden werden auch als “Minify” bzw. “Minification” bezeichnet. Dabei helfen folgende Tools:

Auch der Coverage-Report in den Chrome Developer Tools kann helfen ungenutzten Code zu finden. Der Report zeigt uns die genutzten CSS- und JS-Dateien und wie viel und welcher Code davon nicht benötigt wird:

Kritische Ressourcen minimieren

Von einer “kritischen Ressource” spricht man, wenn diese das erste Rendern der Seite blockieren kann. Je weniger Ressourcen verwendet werden, desto weniger muss der Browser arbeiten. Vor allem bei CSS- und JavaScript-Dateien ist das kritisch (je weniger Dateien desto besser).

Um kritische CSS-Ressourcen zu minimieren, empfiehlt sich der Einsatz von CSS “Media Types” and “Media Queries”. Die “Media Types” kann man verschiedene Stylesheets für verschiedene Media-Typen angeben. Je nachdem mit welchen Medien-Typ die Seite aufgerufen wird, kann ein passenden Stylesheet aufgerufen werden. Die bekanntesten Media-Typen sind:

  • all: Das CSS wird für alle Geräte ausgegeben.
  • print: Das CSS wird nur für die Druckversion ausgegeben.
  • screen: Stylesheet für alle Bildschirme.
  • handheld: Stylesheet für alle Mobiltelefone.

Mit “Media Queries” – die dann später in CSS gekommen sind – kann man auch basierend auf der Breite des Anzeigenmediums passende Stylesheets zurückgeben. Sehen wir uns folgende CSS-Angaben als Beispiel an:

<link href="style.css" rel="stylesheet">
<link href="print.css" rel="stylesheet" media="print">
<link href="other.css" rel="stylesheet" media="(min-width: 320px)">

Im ersten Bespiel gibt es keine Medienabfrage, daher wird dieses CSS in allen Medien zur Verfügung gestellt, wodurch das Rendering blockiert wird. Im zweiten Fall wird das CSS nur bei der Druckversion angewendet (hier kommt ein “Media Type” zum Einsatz). Beim Laden der Seite wird dieses CSS das Seiten-Rendering nicht blockieren, wenn es nicht benötigt wird. Die dritte Zeile enthält eine Bedingung, die vom Browser erfüllt werden muss. Nur wenn die Seite min. 320 Pixel breit ist (“Media Query”), wird dieses CSS benötigt. In diesem Fall wird dann das Rendering blockiert.

Viele Webseiten stellen ein allgemeines CSS (style.css) und ein Druck-CSS (print.css) zur Verfügung, vergessen aber die Medienabfrage bei der Druckversion. Hier muss dann der Browser 2 renderblockierende CSS bearbeiten (obwohl 1 CSS zum Zeitpunkt des Ladens nicht benötigt wird). Besser wäre es in diesem Fall die Druckdatei mit media=”print” zu versehen.

Neben CSS spielt auch JavaScript eine wesentliche Rolle. Denn das Laden eines Inline JS-Skripts und eines Skript-Tags blockiert die DOM-Konstruktion und verzögert dadurch das Rendering. Vor allem bei externen Skripten muss der Browser das Rendering stoppen, um eine externe Verbindung aufzubauen und das Skript anzufordern und zu laden. Standardmäßig ist also auch JavaScript render-blocking. Jedoch kann man den Browser ein Signal geben, dass das JS nicht genau an der Stelle wo es gefunden wird (z.B. im Head oder mitten im Body), ausgeführt werden muss. Mit dem Keyword “async” kann man den Browser mitteilen, dass mit der DOM-Konstruktion fortgefahren und das Skript erst ausgerufen werden kann, wenn es heruntergeladen wurde und bereit steht.

“async” macht vor allem bei externen Skripten (z.B. Analytics-Skripte) Sinn. Bei internen Skripten sollte man nochmal prüfen, ob das angewendet werden kann. Alternative Methoden wäre das JavaScript am Ende zu laden oder mit defer zu arbeiten. Zusammengefasst gibt es also folgende Möglichkeiten:

  • Mit asnyc JavaScript asynchron laden
  • Mit defer JavaScript verzögert laden
  • JavaScript am Ende laden lassen

Critical Rendering Path Länge reduzieren

Um die Länge des CRP zu reduzieren, sollten im besten Fall alle notwendigen Dateien im ersten Roundtrip mitgegeben werden, der ca. 14 KB schwer sein kann.

Was ist damit genau gemeint?
Bevor der Browser anfängt eine Seite zu rendern, geschieht vorher folgendes:

  • Nutzer gibt die URL in die Adresszeile ein
  • DNS Lookup: Die Domain wird in eine IP-Adresse umgewandelt (ca. 200 Millisekunden)
  • TCP Connection (ca. 200 Millisekunden)
  • HTTP-Request und HTTP-Response (ca. 200 Millisekunden)
  • Server-Antwortzeit (im besten Fall ca. 200 Millisekunden)
  • Rendering (im besten Fall ca. 200 Millisekunden)

Für den Critical Rendering Path sollte das alles im besten Fall innerhalb 1 Sekunde passieren, damit die Seite für den Nutzer als “schnell” wahrgenommen wird. Damit wir diese Zeit erreichen können, ist es wichtig, dass wir neben der Optimierung der Server-Antwortzeit sowie von CSS- und JS-Dateien, dafür sorgen, dass alle notwendigen Dateien im ersten Roundtrip (auch Roundtrip Time – Abk. RRT – genannt) mitgegeben werden: Der oben genannte Prozess muss für jede Ressource durchgeführt werden. Der Webseiteninhalt wird dabei in Stücken heruntergeladen. Das erste Stück bzw. Paket, was der Server schickt hat eine maximale Größe von ca. 14 KB. Wenn wir es schaffen unsere Dateien so zu optimieren, dass diese für den kritischen Rendering-Pfad nicht mehr als 14 KB groß sind, können wir die maximal mögliche Seitenladegeschwindigkeit erreichen. Sollte der Critical Rendering Path z.B. 15 KB groß sein, müssen schon 2 Roundtrips stattfinden. Hinweis: Dies gilt für HTTP/1.x, bei HTTP/2 werden alle Dateien innerhalb eines Pakets mitgegeben.

Critical CSS und JavaScript auslagern

Bei diesem Punkt geht es darum die CSS- und JavaScript-Datei zu teilen:

  • 1. Teil: Alle Angaben, die für das Laden des kritischen Rendering-Pfades gebraucht werden, sollten inline im Head-Bereich der Seite geladen werden.
  • 2. Teil: Alle Angaben, die nicht für das Laden des kritischen Rendering-Pfades gebraucht werden, sollten in eine separate Datei gepackt werden und können am Seitenende geladen werden.

Weitere Tipps zur Optimierung

Neben den oben genannten Techniken nachfolgend noch ein paar weitere Tipps zur Optimierung, die nicht nur auf den kritischen Rendering-Pfad Auswirkungen haben, sondern die allgemeine Performance beeinflussen.

Unnötige Downloads beseitigen

Auf Webseiten werden immer mehr und öfter neue Techniken eingesetzt (z.B. spezielle Bildergalerien), die HTTP-Requests verursachen. Um auf der Webseite unnötige Ressourcen zu identifizieren, sind folgende Schritte sinnvoll:

  1. Erstellung Liste aller Ressourcen auf der aktuellen Seite
  2. Messung der Leistung der einzelnen Ressourcen
  3. Beurteilung, ob die Ressourcen einen Mehrwert bieten

Sicherlich wird man auf Elemente stoßen, die unnötig sind und die Performance der Seite beeinträchtigen. Beispielsweise könnte ein Slider im Einsatz sein, mit dem man sich durch 10 Bilder durchklicken kann. Folgende Fragen kann man sich dabei z.B. stellen:

  • Wie schnell laden die Bilder?
  • Sind die Bilder optimiert bzw. komprimiert?
  • Klicken sich Nutzer wirklich komplett durch oder würden 3 Bilder auch reichen?
  • Braucht man den Slider überhaupt noch?
  • Bietet diese Technik den Nutzern einen Mehrwert?

Beachte, dass die Liste nicht nur eigene Ressourcen beinhalten soll, sondern auch Tools und Elemente von Drittanbietern (z.B. Social Icons, die sehr oft einiges an HTTP-Requests generieren). Bei der Beurteilung, ob Ressourcen minimiert oder gar eliminiert werden sollten, macht es Sinn auch Messungen wie A/B-Tests durchzuführen, um die Nutzer-Interaktion mit den betroffenen Elementen (z.B. Bildergalerie) zu messen.

Bilder optimieren

Die Bildoptimierung wird hier ausführlich thematisiert.

Webfonts optimieren

Über die Optimierung von Schriftarten kannst du hier mehr erfahren.