Laden von Ressourcen optimieren

Im vorherigen Modul haben wir einige theoretische Grundlagen zum kritischen Rendering-Pfad kennengelernt und erfahren, wie render- und parserblockierende Ressourcen das erste Rendern einer Seite verzögern können. Nachdem Sie sich mit der Theorie vertraut gemacht haben, können Sie sich nun einige Techniken zur Optimierung des kritischen Rendering-Pfads ansehen.

Beim Laden einer Seite werden in ihrem HTML-Code viele Ressourcen referenziert, die der Seite durch CSS ihr Erscheinungsbild und Layout sowie durch JavaScript ihre Interaktivität verleihen. In diesem Modul werden einige wichtige Konzepte im Zusammenhang mit diesen Ressourcen und deren Auswirkungen auf die Ladezeit einer Seite behandelt.

Blockierung des Renderings

Wie im vorherigen Modul erläutert, ist CSS eine Ressource, die das Rendering blockiert, da sie den Browser daran hindert, Inhalte zu rendern, bis das CSS Object Model (CSSOM) erstellt wurde. Der Browser blockiert das Rendern, um ein FOUC (Flash of Unstyled Content) zu verhindern, was aus Nutzersicht unerwünscht ist.

Im vorherigen Video ist kurz ein FOUC zu sehen, bei dem die Seite ohne Formatierung angezeigt wird. Anschließend werden alle Stile angewendet, sobald das CSS der Seite aus dem Netzwerk geladen wurde. Die Version der Seite ohne Stil wird sofort durch die Version mit Stil ersetzt.

Normalerweise sehen Sie keinen FOUC. Es ist jedoch wichtig, das Konzept zu verstehen, damit Sie wissen, warum der Browser das Rendern der Seite blockiert, bis das CSS heruntergeladen und auf die Seite angewendet wurde. Render-Blocking ist nicht unbedingt unerwünscht, aber Sie sollten die Dauer durch optimiertes CSS minimieren.

Parser blockieren

Eine parser-blockierende Ressource unterbricht den HTML-Parser, z. B. ein <script>-Element ohne async- oder defer-Attribute. Wenn der Parser auf ein <script>-Element trifft, muss der Browser das Script auswerten und ausführen, bevor er mit dem Parsen des restlichen HTML-Codes fortfährt. Das ist so gewollt, da Skripts das DOM möglicherweise zu einem Zeitpunkt ändern oder darauf zugreifen, zu dem es noch erstellt wird.

<!-- This is a parser-blocking script: -->
<script src="/script.js"></script>

Wenn externe JavaScript-Dateien (ohne async oder defer) verwendet werden, wird der Parser blockiert, sobald die Datei erkannt wird, bis sie heruntergeladen, geparst und ausgeführt wird. Bei der Verwendung von Inline-JavaScript wird der Parser ebenfalls blockiert, bis das Inline-Script geparst und ausgeführt wird.

Der Preload-Scanner

Der Preload Scanner ist eine Browseroptimierung in Form eines sekundären HTML-Parsers, der die rohe HTML-Antwort scannt, um Ressourcen zu finden und spekulativ abzurufen, bevor der primäre HTML-Parser sie entdecken würde. Mit dem Scanner zum Vorabladen kann der Browser beispielsweise mit dem Herunterladen einer Ressource beginnen, die in einem <img>-Element angegeben ist, auch wenn der HTML-Parser blockiert wird, während Ressourcen wie CSS und JavaScript abgerufen und verarbeitet werden.

Damit der Preload-Scanner genutzt werden kann, sollten kritische Ressourcen in das vom Server gesendete HTML-Markup aufgenommen werden. Die folgenden Muster für das Laden von Ressourcen können vom Preload-Scanner nicht erkannt werden:

  • Bilder, die von CSS mit dem Attribut background-image geladen werden. Diese Bildreferenzen befinden sich im CSS und können vom Preload-Scanner nicht erkannt werden.
  • Dynamisch geladene Skripts in Form von <script>-Elementmarkup, das mithilfe von JavaScript in das DOM eingefügt wird, oder Module, die mit dynamischen import() geladen werden.
  • HTML, das auf dem Client mit JavaScript gerendert wird. Solches Markup ist in Strings in JavaScript-Ressourcen enthalten und kann vom Preload-Scanner nicht erkannt werden.
  • @import-Deklarationen für Preisvergleichsportale.

Bei diesen Mustern zum Laden von Ressourcen handelt es sich um Ressourcen, die erst spät erkannt werden. Daher profitieren sie nicht vom Preload-Scanner. Vermeiden Sie sie nach Möglichkeit. Wenn es nicht möglich ist, solche Muster zu vermeiden, können Sie möglicherweise einen preload-Hinweis verwenden, um Verzögerungen bei der Ressourcenerkennung zu vermeiden.

CSS

Mit CSS wird die Darstellung und das Layout einer Seite festgelegt. Wie bereits beschrieben, ist CSS eine rendern-blockierende Ressource. Wenn Sie Ihr CSS optimieren, kann sich das erheblich auf die gesamte Seitenladezeit auswirken.

Reduzierung

Durch das Reduzieren von CSS-Dateien wird die Dateigröße einer CSS-Ressource verringert, sodass sie schneller heruntergeladen werden kann. Dies geschieht in erster Linie, indem Inhalte wie Leerzeichen und andere unsichtbare Zeichen aus einer Quell-CSS-Datei entfernt und das Ergebnis in eine neu optimierte Datei ausgegeben wird:

/* Unminified CSS: */

/* Heading 1 */
h1 {
  font-size: 2em;
  color: #000000;
}

/* Heading 2 */
h2 {
  font-size: 1.5em;
  color: #000000;
}
/* Minified CSS: */
h1,h2{color:#000}h1{font-size:2em}h2{font-size:1.5em}

Die CSS-Minimierung ist eine effektive Optimierung, die den FCP Ihrer Website und in einigen Fällen sogar den LCP verbessern kann. Tools wie Bundler können diese Optimierung in Produktions-Builds automatisch für Sie durchführen.

Nicht verwendete CSS entfernen

Bevor Inhalte gerendert werden können, müssen alle Stylesheets heruntergeladen und geparst werden. Die für das Parsen benötigte Zeit umfasst auch Stile, die auf der aktuellen Seite nicht verwendet werden. Wenn Sie einen Bundler verwenden, der alle CSS-Ressourcen in einer einzigen Datei zusammenfasst, laden Ihre Nutzer wahrscheinlich mehr CSS herunter, als zum Rendern der aktuellen Seite erforderlich ist.

Wenn Sie ungenutztes CSS für die aktuelle Seite ermitteln möchten, verwenden Sie das Tool zur Codeabdeckung in den Chrome-Entwicklertools.

Ein Screenshot des Tools „Abdeckung“ in den Chrome-Entwicklertools. Im unteren Bereich ist eine CSS-Datei ausgewählt, die eine beträchtliche Menge an CSS enthält, die vom aktuellen Seitenlayout nicht verwendet wird.
Mit dem Tool zur Codeabdeckung in den Chrome-Entwicklertools lässt sich CSS (und JavaScript) erkennen, das von der aktuellen Seite nicht verwendet wird. Damit können CSS-Dateien in mehrere Ressourcen aufgeteilt werden, die von verschiedenen Seiten geladen werden. So wird verhindert, dass ein viel größeres CSS-Bundle übertragen wird, das das Rendern der Seite verzögern kann.

Das Entfernen von nicht verwendetem CSS-Code hat einen doppelten Effekt: Neben der Reduzierung der Downloadzeit wird auch die Erstellung des Renderbaums optimiert, da der Browser weniger CSS-Regeln verarbeiten muss.

CSS-@import-Deklarationen vermeiden

Auch wenn es praktisch erscheint, sollten Sie @import-Deklarationen in CSS vermeiden:

/* Don't do this: */
@import url('style.css');

Ähnlich wie das <link>-Element in HTML können Sie mit der @import-Deklaration in CSS eine externe CSS-Ressource aus einem Stylesheet importieren. Der Hauptunterschied zwischen diesen beiden Ansätzen besteht darin, dass das HTML-Element <link> Teil der HTML-Antwort ist und daher viel früher erkannt wird als eine CSS-Datei, die durch eine @import-Deklaration heruntergeladen wird.

Der Grund dafür ist, dass die CSS-Datei, die die @import-Deklaration enthält, zuerst heruntergeladen werden muss, damit die Deklaration erkannt wird. Dies führt zu einer Anfragekette, die im Fall von CSS die Zeit bis zum ersten Rendern einer Seite verzögert. Ein weiterer Nachteil ist, dass Stylesheets, die mit einer @import-Deklaration geladen werden, nicht vom Preload-Scanner erkannt werden können und daher erst spät erkannte, rendern-blockierende Ressourcen werden.

<!-- Do this instead: -->
<link rel="stylesheet" href="style.css">

In den meisten Fällen können Sie @import durch ein <link rel="stylesheet">-Element ersetzen. Mit <link>-Elementen können Stylesheets gleichzeitig heruntergeladen werden, was die gesamte Ladezeit im Vergleich zu @import-Deklarationen verkürzt, bei denen Stylesheets nacheinander heruntergeladen werden.

Wichtiges CSS (Inline)

Die Zeit, die zum Herunterladen von CSS-Dateien benötigt wird, kann den FCP einer Seite erhöhen. Durch das Inline-Einbinden kritischer Stile im Dokument <head> wird die Netzwerkanfrage für eine CSS-Ressource vermieden. Wenn dies richtig erfolgt, können die anfänglichen Ladezeiten verbessert werden, wenn der Browsercache eines Nutzers nicht vorbereitet ist. Das verbleibende CSS kann asynchron geladen oder am Ende des <body>-Elements angehängt werden.

<head>
  <title>Page Title</title>
  <!-- ... -->
  <style>h1,h2{color:#000}h1{font-size:2em}h2{font-size:1.5em}</style>
</head>
<body>
  <!-- Other page markup... -->
  <link rel="stylesheet" href="non-critical.css">
</body>

Andererseits werden durch das Inline-Einbinden einer großen Menge an CSS der ursprünglichen HTML-Antwort zusätzliche Byte hinzugefügt. Da HTML-Ressourcen oft nicht sehr lange oder gar nicht gecacht werden können, wird das Inline-CSS nicht für nachfolgende Seiten gecacht, die möglicherweise dasselbe CSS in externen Stylesheets verwenden. Testen Sie die Leistung Ihrer Seite und messen Sie sie, um sicherzugehen, dass sich die Kompromisse lohnen.

CSS-Demos

JavaScript

JavaScript ist für die meisten interaktiven Elemente im Web verantwortlich, aber das hat seinen Preis. Wenn zu viel JavaScript ausgeliefert wird, kann es sein, dass Ihre Webseite während des Seitenaufrufs nur langsam reagiert. Das kann sogar zu Reaktionsfähigkeitsproblemen führen, die Interaktionen verlangsamen. Beides kann für Nutzer frustrierend sein.

JavaScript, das das Rendering blockiert

Wenn <script>-Elemente ohne die Attribute defer oder async geladen werden, blockiert der Browser das Parsen und Rendern, bis das Skript heruntergeladen, geparst und ausgeführt wurde. Ebenso blockieren Inline-Skripts den Parser, bis das Skript geparst und ausgeführt wurde.

async im Vergleich zu defer

Mit async und defer können externe Skripts geladen werden, ohne den HTML-Parser zu blockieren. Skripts (einschließlich Inline-Skripts) mit type="module" werden automatisch verzögert. async und defer weisen jedoch einige wichtige Unterschiede auf.

Eine Darstellung verschiedener Mechanismen zum Laden von Skripts, in denen die Rollen von Parser, Abruf und Ausführung basierend auf verschiedenen Attributen wie „async“, „defer“, „type=&#39;module&#39;“ und einer Kombination aus allen drei beschrieben werden.
Quelle: https://html.spec.whatwg.org/multipage/scripting.html

Mit async geladene Skripts werden sofort nach dem Herunterladen geparst und ausgeführt. Mit defer geladene Skripts werden ausgeführt, wenn das Parsen des HTML-Dokuments abgeschlossen ist. Das ist gleichzeitig mit dem DOMContentLoaded-Ereignis des Browsers. Außerdem werden async-Scripts möglicherweise in einer anderen Reihenfolge als im Markup angegeben ausgeführt, während defer-Scripts in der Reihenfolge ausgeführt werden, in der sie im Markup erscheinen.

Clientseitiges Rendering

Im Allgemeinen sollten Sie JavaScript vermeiden, um wichtige Inhalte oder das LCP-Element einer Seite zu rendern. Das wird als clientseitiges Rendern bezeichnet und ist eine Technik, die häufig in Single-Page-Anwendungen (SPAs) verwendet wird.

Von JavaScript gerendertes Markup umgeht den Preload-Scanner, da die in dem clientseitig gerenderten Markup enthaltenen Ressourcen nicht erkannt werden können. Dadurch kann sich der Download wichtiger Ressourcen wie eines LCP-Bilds verzögern. Der Browser beginnt erst mit dem Herunterladen des LCP-Bildes, nachdem das Skript ausgeführt und das Element dem DOM hinzugefügt wurde. Das Skript kann wiederum erst ausgeführt werden, nachdem es erkannt, heruntergeladen und geparst wurde. Dies wird als Kette kritischer Anfragen bezeichnet und sollte vermieden werden.

Außerdem ist es wahrscheinlicher, dass beim Rendern von Markup mit JavaScript lange Aufgaben generiert werden als bei Markup, das als Antwort auf eine Navigationsanfrage vom Server heruntergeladen wird. Die intensive Nutzung von clientseitigem Rendern von HTML kann sich negativ auf die Interaktionslatenz auswirken. Das gilt insbesondere dann, wenn das DOM einer Seite sehr groß ist. In diesem Fall ist der Rendering-Aufwand erheblich, wenn JavaScript das DOM ändert.

Reduzierung

Ähnlich wie bei CSS wird durch das Reduzieren von JavaScript die Dateigröße einer Skriptressource verringert. Das kann zu schnelleren Downloads führen, sodass der Browser schneller mit dem Parsen und Kompilieren von JavaScript beginnen kann.

Außerdem geht die Komprimierung von JavaScript noch einen Schritt weiter als die Komprimierung anderer Assets wie CSS. Beim Minimieren von JavaScript werden nicht nur Leerzeichen, Tabulatoren und Kommentare entfernt, sondern auch Symbole im Quell-JavaScript werden gekürzt. Dieser Vorgang wird manchmal als uglification bezeichnet. Um den Unterschied zu sehen, betrachten Sie den folgenden JavaScript-Quellcode:

// Unuglified JavaScript source code:
export function injectScript () {
  const scriptElement = document.createElement('script');
  scriptElement.src = '/js/scripts.js';
  scriptElement.type = 'module';

  document.body.appendChild(scriptElement);
}

Wenn der oben genannte JavaScript-Quellcode komprimiert wird, kann das Ergebnis so aussehen:

// Uglified JavaScript production code:
export function injectScript(){const t=document.createElement("script");t.src="/js/scripts.js",t.type="module",document.body.appendChild(t)}

Im vorherigen Snippet sehen Sie, dass die für Menschen lesbare Variable scriptElement in der Quelle auf t verkürzt wird. Bei einer großen Sammlung von Skripten können die Einsparungen erheblich sein, ohne dass die Funktionen des Produktions-JavaScript einer Website beeinträchtigt werden.

Wenn Sie einen Bundler verwenden, um den Quellcode Ihrer Website zu verarbeiten, wird die Uglifizierung häufig automatisch für Produktions-Builds durchgeführt. Uglifiers wie Terser sind ebenfalls hochgradig konfigurierbar. So können Sie die Aggressivität des Uglification-Algorithmus anpassen, um maximale Einsparungen zu erzielen. Die Standardeinstellungen für die meisten Tools zur Code-Minimierung reichen jedoch in der Regel aus, um ein ausgewogenes Verhältnis zwischen Ausgabegröße und Erhalt der Funktionen zu erzielen.

JavaScript-Demos

Wissen testen

Wie lade ich mehrere CSS-Dateien am besten in den Browser?

Mehrere <link>-Elemente.
Die @import-Deklaration der CSS.

Was macht der Browser-Scanner zum Vorabladen?

Erkennt <link rel="preload">-Elemente in einer HTML-Ressource.
Es handelt sich um einen sekundären HTML-Parser, der das Roh-Markup untersucht, um Ressourcen zu erkennen, bevor der DOM-Parser dies tun kann.

Warum blockiert der Browser das Parsen von HTML standardmäßig vorübergehend, wenn JavaScript-Ressourcen heruntergeladen werden?

So verhindern Sie ein FOUC (Flash of Unstyled Content).
Weil Scripts das DOM ändern oder anderweitig darauf zugreifen können.
Da die Auswertung von JavaScript eine sehr CPU-intensive Aufgabe ist, wird durch das Pausieren des HTML-Parsings mehr Bandbreite für die CPU geschaffen, um das Laden von Scripts abzuschließen.

Nächster Schritt: Browser mit Ressourcenhinweisen unterstützen

Nachdem Sie nun wissen, wie sich Ressourcen, die im <head>-Element geladen werden, auf den anfänglichen Seitenaufbau und verschiedene Messwerte auswirken können, ist es an der Zeit, fortzufahren. Im nächsten Modul werden Ressourcenhinweise behandelt. Sie können dem Browser wertvolle Hinweise geben, damit er Ressourcen schneller lädt und Verbindungen zu serverübergreifenden Servern schneller öffnet, als er es ohne sie tun würde.