Bekämpfe den Vorablade-Scanner des Browsers nicht

Hier erfahren Sie, was der Preload-Scanner des Browsers ist, wie er die Leistung verbessert und wie Sie ihn nicht behindern.

Ein oft übersehener Aspekt bei der Optimierung der Page Speed ist das Wissen über die internen Abläufe im Browser. Browser nehmen bestimmte Optimierungen vor, um die Leistung auf eine Weise zu verbessern, die wir als Entwickler nicht erreichen können – aber nur, solange diese Optimierungen nicht unbeabsichtigt verhindert werden.

Eine interne Browseroptimierung, die Sie kennen sollten, ist der Preload-Scanner des Browsers. In diesem Beitrag erfahren Sie, wie der Preload-Scanner funktioniert und vor allem, wie Sie vermeiden können, dass er beeinträchtigt wird.

Was ist ein Preload-Scanner?

Jeder Browser hat einen primären HTML-Parser, der Roh-Markup tokenisiert und in ein Objektmodell umwandelt. Das geht so lange weiter, bis der Parser eine blockierende Ressource findet, z. B. ein Stylesheet, das mit einem <link>-Element geladen wird, oder ein Script, das mit einem <script>-Element ohne async- oder defer-Attribut geladen wird.

Diagramm des HTML-Parsers.
Abb. 1: Diagramm, das zeigt, wie der primäre HTML-Parser des Browsers blockiert werden kann. In diesem Fall stößt der Parser auf ein <link>-Element für eine externe CSS-Datei. Dadurch wird der Browser daran gehindert, den Rest des Dokuments zu parsen oder überhaupt zu rendern, bis die CSS-Datei heruntergeladen und geparst wurde.

Bei CSS-Dateien wird das Rendering blockiert, um ein Aufblitzen von unformatierten Inhalten (FOUC) zu verhindern. Dabei ist eine unformatierte Version einer Seite kurz zu sehen, bevor Stile darauf angewendet werden.

Die web.dev-Startseite im unformatierten Zustand (links) und im formatierten Zustand (rechts).
Abb. 2: Simuliertes Beispiel für FOUC. Links sehen Sie die Startseite von web.dev ohne Stile. Rechts ist dieselbe Seite mit angewendeten Stilen zu sehen. Der unformatierte Zustand kann blitzschnell auftreten, wenn der Browser das Rendering nicht blockiert, während ein Stylesheet heruntergeladen und verarbeitet wird.

Der Browser blockiert auch das Parsen und Rendern der Seite, wenn er auf <script>-Elemente ohne defer- oder async-Attribut stößt.

Der Grund dafür ist, dass der Browser nicht sicher wissen kann, ob ein bestimmtes Skript das DOM ändert, während der primäre HTML-Parser noch seine Arbeit erledigt. Aus diesem Grund ist es gängige Praxis, JavaScript am Ende des Dokuments zu laden, damit die Auswirkungen des blockierten Parsings und Renderings gering sind.

Das sind gute Gründe, warum der Browser sowohl das Parsen als auch das Rendern blockieren sollte. Das Blockieren dieser wichtigen Schritte ist jedoch nicht wünschenswert, da es die Entdeckung anderer wichtiger Ressourcen verzögern kann. Glücklicherweise versuchen Browser, diese Probleme mithilfe eines sekundären HTML-Parsers, dem sogenannten Preload-Scanner, zu minimieren.

Ein Diagramm des primären HTML-Parsers (links) und des Preload-Scanners (rechts), der der sekundäre HTML-Parser ist.
Abbildung 3: Ein Diagramm, das zeigt, wie der Preload Scanner parallel zum primären HTML-Parser arbeitet, um Assets spekulativ zu laden. Hier wird der primäre HTML-Parser blockiert, da er CSS lädt und verarbeitet, bevor er mit der Verarbeitung des Bild-Markups im <body>-Element beginnen kann. Der Preload-Scanner kann jedoch im Roh-Markup nachsehen, um diese Bildressource zu finden und mit dem Laden zu beginnen, bevor der primäre HTML-Parser entsperrt wird.

Die Rolle eines Preload-Scanners ist spekulativ, d. h., er untersucht das Roh-Markup, um Ressourcen zu finden, die er opportunistisch abrufen kann, bevor der primäre HTML-Parser sie sonst entdecken würde.

So erkennen Sie, ob der Preload-Scanner funktioniert

Der Preload-Scanner existiert aufgrund von blockiertem Rendering und Parsing. Wenn diese beiden Leistungsprobleme nicht existieren würden, wäre der Preload-Scanner nicht sehr nützlich. Ob eine Webseite vom Preload-Scanner profitiert, hängt von diesen blockierenden Phänomenen ab. Dazu können Sie eine künstliche Verzögerung für Anfragen einführen, um herauszufinden, wo der Preload-Scanner arbeitet.

Nehmen wir als Beispiel diese Seite mit einfachem Text und Bildern mit einem Stylesheet. Da CSS-Dateien sowohl das Rendering als auch das Parsen blockieren, wird über einen Proxy-Dienst eine künstliche Verzögerung von zwei Sekunden für das Stylesheet eingeführt. Durch diese Verzögerung ist im Netzwerk-Wasserfalldiagramm leichter zu erkennen, wo der Preload-Scanner arbeitet.

Das WebPageTest-Netzwerk-Wasserfalldiagramm zeigt eine künstliche Verzögerung von 2 Sekunden, die dem Stylesheet auferlegt wurde.
Abb. 4: Ein Netzwerk-Wasserfalldiagramm von WebPageTest für eine Webseite, die in Chrome auf einem Mobilgerät über eine simulierte 3G-Verbindung ausgeführt wird. Obwohl das Stylesheet durch einen Proxy künstlich um zwei Sekunden verzögert wird, bevor es geladen wird, wird das Bild, das sich später in der Markup-Nutzlast befindet, vom Preload-Scanner erkannt.

Wie Sie im Wasserfalldiagramm sehen können, erkennt der Preload-Scanner das <img>-Element, obwohl das Rendering und das Parsen des Dokuments blockiert sind. Ohne diese Optimierung kann der Browser während der Blockierungsphase keine Ressourcen abrufen und mehr Ressourcenanfragen würden nacheinander statt gleichzeitig erfolgen.

Nachdem wir dieses einfache Beispiel hinter uns gelassen haben, sehen wir uns nun einige reale Muster an, bei denen der Preload-Scanner umgangen werden kann, und was getan werden kann, um das Problem zu beheben.

async eingeschleuste Skripts

Angenommen, Sie haben HTML-Code in Ihrem <head>, der Inline-JavaScript enthält, z. B.:

<script>
  const scriptEl = document.createElement('script');
  scriptEl.src = '/yall.min.js';

  document.head.appendChild(scriptEl);
</script>

Eingefügte Skripts sind standardmäßig async. Wenn dieses Skript eingefügt wird, verhält es sich also so, als wäre das Attribut async darauf angewendet worden. Das bedeutet, dass es so schnell wie möglich ausgeführt wird und das Rendering nicht blockiert. Klingt optimal, oder? Wenn Sie jedoch davon ausgehen, dass dieses Inline-<script> nach einem <link>-Element kommt, das eine externe CSS-Datei lädt, erhalten Sie ein suboptimales Ergebnis:

Dieses WebPageTest-Diagramm zeigt, dass der Preload-Scan fehlschlägt, wenn ein Script eingefügt wird.
Abb. 5: Ein WebPageTest-Netzwerk-Wasserfalldiagramm einer Webseite, die in Chrome auf einem Mobilgerät über eine simulierte 3G-Verbindung ausgeführt wird. Die Seite enthält ein einzelnes Stylesheet und ein eingefügtes async-Skript. Der Preload-Scanner kann das Script während der Phase, in der das Rendering blockiert wird, nicht finden, da es auf dem Client eingefügt wird.

Sehen wir uns das mal genauer an:

  1. Bei 0 Sekunden wird das Hauptdokument angefordert.
  2. Nach 1, 4 Sekunden trifft das erste Byte der Navigationsanfrage ein.
  3. Nach 2, 0 Sekunden werden das CSS und das Bild angefordert.
  4. Da der Parser durch das Laden des Stylesheets blockiert wird und das Inline-JavaScript, das das async-Skript einfügt, nach diesem Stylesheet bei 2,6 Sekunden kommt, ist die Funktionalität, die das Skript bietet, nicht so schnell verfügbar, wie es möglich wäre.

Das ist nicht optimal, da die Anfrage für das Script erst erfolgt, nachdem das Stylesheet heruntergeladen wurde. Dadurch wird die Ausführung des Skripts verzögert. Das <img>-Element ist dagegen im vom Server bereitgestellten Markup sichtbar und wird daher vom Preload-Scanner erkannt.

Was passiert, wenn Sie ein reguläres <script>-Tag mit dem Attribut async verwenden, anstatt das Script in das DOM einzufügen?

<script src="/yall.min.js" async></script>

Das Ergebnis lautet:

Ein WebPageTest-Netzwerk-Wasserfalldiagramm, das zeigt, wie ein asynchrones Skript, das mit dem HTML-Skriptelement geladen wird, vom Preload-Scanner des Browsers erkannt werden kann, obwohl der primäre HTML-Parser des Browsers beim Herunterladen und Verarbeiten eines Stylesheets blockiert ist.
Abb. 6: Ein WebPageTest-Netzwerk-Wasserfalldiagramm einer Webseite, die in Chrome auf einem Mobilgerät über eine simulierte 3G-Verbindung ausgeführt wird. Die Seite enthält ein einzelnes Stylesheet und ein einzelnes async-<script>-Element. Der Preload-Scanner entdeckt das Skript während der Render-Blocking-Phase und lädt es gleichzeitig mit dem CSS.

Es könnte die Versuchung bestehen, vorzuschlagen, dass diese Probleme durch die Verwendung von rel=preload behoben werden könnten. Das würde sicherlich funktionieren, könnte aber einige Nebenwirkungen haben. Warum sollte man rel=preload verwenden, um ein Problem zu beheben, das sich vermeiden lässt, indem man kein <script>-Element in das DOM einfügt?

Ein WebPageTest-Wasserfalldiagramm, das zeigt, wie der Ressourcenhinweis „rel=preload“ verwendet wird, um die Erkennung eines asynchron eingefügten Skripts zu fördern – allerdings auf eine Weise, die unbeabsichtigte Nebenwirkungen haben kann.
Abb. 7: Ein WebPageTest-Netzwerk-Wasserfalldiagramm einer Webseite, die in Chrome auf einem Mobilgerät über eine simulierte 3G-Verbindung ausgeführt wird. Die Seite enthält ein einzelnes Stylesheet und ein eingefügtes async-Script. Das async-Script wird jedoch vorab geladen, damit es früher erkannt wird.

Durch das Vorabladen wird das Problem hier „behoben“, aber es entsteht ein neues Problem: Das Script async in den ersten beiden Demos wird, obwohl es im <head> geladen wird, mit der Priorität „Niedrig“ geladen, während das Stylesheet mit der Priorität „Höchste“ geladen wird. In der letzten Demo, in der das Script async vorab geladen wird, wird das Stylesheet weiterhin mit der Priorität „Höchste“ geladen, die Priorität des Scripts wurde jedoch auf „Hoch“ heraufgestuft.

Wenn die Priorität einer Ressource erhöht wird, weist der Browser ihr mehr Bandbreite zu. Das bedeutet, dass es trotz der höchsten Priorität des Stylesheets zu Bandbreitenkonflikten kommen kann, weil die Priorität des Scripts erhöht wurde. Das kann bei langsamen Verbindungen oder bei sehr großen Ressourcen ein Faktor sein.

Die Antwort ist einfach: Wenn ein Script beim Start benötigt wird, sollten Sie den Preload-Scanner nicht umgehen, indem Sie es in das DOM einfügen. Experimentieren Sie nach Bedarf mit der Platzierung des <script>-Elements sowie mit Attributen wie defer und async.

Lazy Loading mit JavaScript

Lazy Loading ist eine hervorragende Methode, um Daten zu sparen, und wird häufig für Bilder verwendet. Manchmal wird Lazy Loading jedoch fälschlicherweise auf Bilder angewendet, die sich sozusagen „above the fold“ befinden.

Dies führt zu potenziellen Problemen bei der Auffindbarkeit von Ressourcen durch den Preload-Scanner und kann die Zeit, die zum Erkennen eines Verweises auf ein Bild, zum Herunterladen, Decodieren und Präsentieren des Bildes benötigt wird, unnötig verlängern. Sehen wir uns zum Beispiel dieses Bild-Markup an:

<img data-src="/sand-wasp.jpg" alt="Sand Wasp" width="384" height="255">

Die Verwendung des Präfixes data- ist ein gängiges Muster bei JavaScript-basierten Lazy Loadern. Wenn das Bild in den Darstellungsbereich gescrollt wird, entfernt der Lazy Loader das Präfix data-. Im vorherigen Beispiel wird data-src also zu src. Durch diese Aktualisierung wird der Browser aufgefordert, die Ressource abzurufen.

Dieses Muster ist erst dann problematisch, wenn es auf Bilder angewendet wird, die sich beim Start im Darstellungsbereich befinden. Da der Preload-Scanner das Attribut data-src nicht auf dieselbe Weise liest wie ein Attribut src (oder srcset), wird der Bildverweis nicht früher erkannt. Schlimmer noch: Das Bild wird erst nach dem Herunterladen, Kompilieren und Ausführen des Lazy-Loader-JavaScripts geladen.

Ein WebPageTest-Netzwerk-Wasserfalldiagramm, das zeigt, wie ein Bild mit Lazy Loading, das sich beim Start im Darstellungsbereich befindet, zwangsläufig verzögert wird, weil der Preload-Scanner des Browsers die Bildressource nicht finden kann und sie erst lädt, wenn das für Lazy Loading erforderliche JavaScript geladen wird. Das Bild wird viel zu spät entdeckt.
Abb. 8: Ein WebPageTest-Netzwerk-Wasserfalldiagramm einer Webseite, die in Chrome auf einem Mobilgerät über eine simulierte 3G-Verbindung ausgeführt wird. Die Bildressource wird unnötigerweise per Lazy Loading geladen, obwohl sie beim Start im Darstellungsbereich sichtbar ist. Dadurch wird der Preload-Scanner umgangen und es kommt zu einer unnötigen Verzögerung.

Je nach Größe des Bildes, die von der Größe des Darstellungsbereichs abhängen kann, ist es möglicherweise ein Kandidat für den Largest Contentful Paint (LCP). Wenn der Preload-Scanner die Bildressource nicht vorab abrufen kann, möglicherweise während die Stylesheets der Seite das Rendering blockieren, leidet der LCP.

Die Lösung besteht darin, das Bild-Markup zu ändern:

<img src="/sand-wasp.jpg" alt="Sand Wasp" width="384" height="255">

Dies ist das optimale Muster für Bilder, die sich beim Start im Darstellungsbereich befinden, da der Preload-Scanner die Bildressource schneller erkennt und abruft.

Ein WebPageTest-Netzwerk-Wasserfalldiagramm, das ein Ladeszenario für ein Bild im Darstellungsbereich während des Starts zeigt. Das Bild wird nicht per Lazy Loading geladen, d. h., es ist nicht vom Skript abhängig, um geladen zu werden. Der Preload-Scanner kann es also früher finden.
Abb. 9: Ein WebPageTest-Netzwerk-Wasserfalldiagramm einer Webseite, die in Chrome auf einem Mobilgerät über eine simulierte 3G-Verbindung ausgeführt wird. Der Preload-Scanner erkennt die Bildressource, bevor das Laden von CSS und JavaScript beginnt. Dadurch hat der Browser einen Vorsprung beim Laden.

Das Ergebnis in diesem vereinfachten Beispiel ist eine Verbesserung des LCP um 100 Millisekunden bei einer langsamen Verbindung. Das mag nicht nach einer großen Verbesserung klingen, aber wenn man bedenkt, dass die Lösung eine schnelle Markup-Korrektur ist und die meisten Webseiten komplexer sind als diese Beispiele, ist es doch eine große Verbesserung. Das bedeutet, dass LCP-Kandidaten möglicherweise mit vielen anderen Ressourcen um Bandbreite konkurrieren müssen. Daher werden Optimierungen wie diese immer wichtiger.

CSS-Hintergrundbilder

Der Preload-Scanner des Browsers scannt Markup. Andere Ressourcentypen wie CSS, bei denen möglicherweise Bilder abgerufen werden, auf die im background-image-Attribut verwiesen wird, werden nicht gescannt.

Wie HTML verarbeiten Browser CSS in ein eigenes Objektmodell, das als CSSOM bezeichnet wird. Wenn beim Erstellen des CSSOM externe Ressourcen gefunden werden, werden diese Ressourcen zum Zeitpunkt der Erkennung und nicht vom Preload-Scanner angefordert.

Angenommen, der LCP-Kandidat Ihrer Seite ist ein Element mit der CSS-Eigenschaft background-image. Beim Laden von Ressourcen geschieht Folgendes:

Ein WebPageTest-Netzwerk-Wasserfalldiagramm, das eine Seite mit einem LCP-Kandidaten zeigt, der über die CSS-Eigenschaft „background-image“ geladen wird. Da das LCP-Kandidatenbild in einem Ressourcentyp vorliegt, den der Preload-Scanner des Browsers nicht untersuchen kann, wird das Laden der Ressource verzögert, bis das CSS heruntergeladen und verarbeitet wurde. Dadurch verzögert sich die Darstellungszeit des LCP-Kandidaten.
Abb. 10: Ein WebPageTest-Netzwerk-Wasserfalldiagramm einer Webseite, die in Chrome auf einem Mobilgerät über eine simulierte 3G-Verbindung ausgeführt wird. Der LCP-Kandidat der Seite ist ein Element mit der CSS-Eigenschaft background-image (Zeile 3). Das angeforderte Bild wird erst abgerufen, wenn der CSS-Parser es findet.

In diesem Fall wird der Preload-Scanner nicht so sehr umgangen, sondern ist einfach nicht beteiligt. Wenn ein LCP-Kandidat auf der Seite aus einer background-image-CSS-Property stammt, solltest du dieses Bild trotzdem vorab laden:

<!-- Make sure this is in the <head> below any
     stylesheets, so as not to block them from loading -->
<link rel="preload" as="image" href="lcp-image.jpg">

Der Hinweis rel=preload ist zwar klein, hilft dem Browser aber, das Bild früher zu finden, als es sonst der Fall wäre:

Ein WebPageTest-Netzwerk-Wasserfalldiagramm, das ein CSS-Hintergrundbild (den LCP-Kandidaten) zeigt, das aufgrund der Verwendung eines rel=preload-Hinweises viel früher geladen wird. Die LCP-Zeit verbessert sich um etwa 250 Millisekunden.
Abb. 11: Ein WebPageTest-Netzwerk-Wasserfalldiagramm einer Webseite, die in Chrome auf einem Mobilgerät über eine simulierte 3G-Verbindung ausgeführt wird. Der LCP-Kandidat der Seite ist ein Element mit der CSS-Eigenschaft background-image (Zeile 3). Der Hinweis rel=preload hilft dem Browser, das Bild etwa 250 Millisekunden früher zu finden als ohne den Hinweis.

Mit dem Hinweis rel=preload wird der LCP-Kandidat früher erkannt, was die LCP-Zeit verkürzt. Dieser Hinweis hilft zwar, das Problem zu beheben, aber es ist möglicherweise besser, zu prüfen, ob der LCP-Kandidat für das Bild aus CSS geladen werden muss. Mit einem <img>-Tag haben Sie mehr Kontrolle darüber, wie ein Bild geladen wird, das für den Darstellungsbereich geeignet ist, und können gleichzeitig dafür sorgen, dass es vom Preload-Scanner gefunden wird.

Zu viele Ressourcen inline einbinden

Beim Inlining wird eine Ressource in den HTML-Code eingefügt. Sie können Stylesheets in <style>-Elementen, Skripts in <script>-Elementen und praktisch jede andere Ressource mit der Base64-Codierung inline einbinden.

Das Inlining von Ressourcen kann schneller sein als das Herunterladen, da für die Ressource keine separate Anfrage gesendet wird. Sie ist direkt im Dokument enthalten und wird sofort geladen. Es gibt jedoch erhebliche Nachteile:

  • Wenn Sie Ihren HTML-Code nicht im Cache speichern – was bei einer dynamischen HTML-Antwort nicht möglich ist –, werden die Inline-Ressourcen nie im Cache gespeichert. Das wirkt sich auf die Leistung aus, da die Inline-Ressourcen nicht wiederverwendbar sind.
  • Selbst wenn Sie HTML im Cache speichern können, werden Inline-Ressourcen nicht zwischen Dokumenten geteilt. Dies verringert die Caching-Effizienz im Vergleich zu externen Dateien, die über einen gesamten Ursprung hinweg im Cache gespeichert und wiederverwendet werden können.
  • Wenn Sie zu viele Inhalte inline einfügen, verzögert sich die Erkennung von Ressourcen später im Dokument durch den Preload-Scanner, da das Herunterladen dieser zusätzlichen Inline-Inhalte länger dauert.

Nehmen wir diese Seite als Beispiel. Unter bestimmten Bedingungen ist der LCP-Kandidat das Bild oben auf der Seite und das CSS befindet sich in einer separaten Datei, die von einem <link>-Element geladen wird. Auf der Seite werden außerdem vier Webschriftarten verwendet, die als separate Dateien von der CSS-Ressource angefordert werden.

Ein WebPageTest-Netzwerk-Wasserfalldiagramm einer Seite mit einer externen CSS-Datei, in der auf vier Schriftarten verwiesen wird. Das LCP-Kandidatenbild wird vom Preload-Scanner rechtzeitig erkannt.
Abb. 12: Ein WebPageTest-Netzwerk-Wasserfalldiagramm einer Webseite, die in Chrome auf einem Mobilgerät über eine simulierte 3G-Verbindung ausgeführt wird. Der LCP-Kandidat der Seite ist ein Bild, das aus einem <img>-Element geladen wird. Es wird jedoch vom Preload-Scanner erkannt, da das CSS und die für den Seitenaufbau erforderlichen Schriftarten in separaten Ressourcen geladen werden, was den Preload-Scanner nicht daran hindert, seine Arbeit zu erledigen.

Was passiert, wenn das CSS und alle Schriftarten als Base64-Ressourcen inline eingebunden werden?

Ein WebPageTest-Netzwerk-Wasserfalldiagramm einer Seite mit einer externen CSS-Datei, in der auf vier Schriftarten verwiesen wird. Der Preload-Scanner wird erheblich verzögert, wenn er das LCP-Bild erkennt .
Abb. 13: Ein WebPageTest-Netzwerk-Wasserfalldiagramm einer Webseite, die in Chrome auf einem Mobilgerät über eine simulierte 3G-Verbindung ausgeführt wird. Der LCP-Kandidat der Seite ist ein Bild, das aus einem <img>-Element geladen wird. Durch das Inlining des CSS und seiner vier Schriftressourcen im `` wird der Preload-Scanner jedoch daran gehindert, das Bild zu erkennen, bis diese Ressourcen vollständig heruntergeladen sind.

Die Auswirkungen des Inlining haben in diesem Beispiel negative Folgen für den LCP und die Leistung im Allgemeinen. Die Version der Seite, die nichts inline einbindet, rendert das LCP-Bild in etwa 3,5 Sekunden. Auf der Seite, auf der alles inline eingebunden ist, wird das LCP-Bild erst nach etwas mehr als 7 Sekunden dargestellt.

Hier ist mehr im Spiel als nur der Preload-Scanner. Das Inlining von Schriftarten ist keine gute Strategie, da Base64 ein ineffizientes Format für binäre Ressourcen ist. Ein weiterer Faktor ist, dass externe Schriftressourcen nur heruntergeladen werden, wenn sie vom CSSOM als notwendig erachtet werden. Wenn diese Schriftarten als Base64 inline eingebunden sind, werden sie heruntergeladen, unabhängig davon, ob sie für die aktuelle Seite benötigt werden oder nicht.

Könnte ein Preload hier Abhilfe schaffen? Klar. Sie könnten das LCP-Bild vorab laden und die LCP-Zeit reduzieren, aber das Aufblähen Ihres potenziell nicht im Cache speicherbaren HTML-Codes mit Inline-Ressourcen hat andere negative Auswirkungen auf die Leistung. Der First Contentful Paint (FCP) ist ebenfalls von diesem Muster betroffen. In der Version der Seite, in der nichts inline eingefügt ist, beträgt der FCP etwa 2,7 Sekunden. In der Version, in der alles inline ist, beträgt der FCP etwa 5,8 Sekunden.

Seien Sie sehr vorsichtig, wenn Sie Inhalte in HTML einfügen, insbesondere Base64-codierte Ressourcen. Im Allgemeinen wird dies nicht empfohlen, außer bei sehr kleinen Ressourcen. Verwenden Sie so wenig Inline-Code wie möglich, da zu viel Inline-Code riskant ist.

Markup mit clientseitigem JavaScript rendern

Es besteht kein Zweifel: JavaScript wirkt sich definitiv auf die Seitengeschwindigkeit aus. Entwickler sind nicht nur auf JavaScript angewiesen, um Interaktivität zu ermöglichen, sondern es gibt auch eine Tendenz, sich darauf zu verlassen, um Inhalte selbst bereitzustellen. Das führt in gewisser Weise zu einer besseren Entwicklererfahrung, aber Vorteile für Entwickler bedeuten nicht immer auch Vorteile für Nutzer.

Ein Muster, das den Preload-Scanner umgehen kann, ist das Rendern von Markup mit clientseitigem JavaScript:

Ein WebPageTest-Netzwerk-Wasserfalldiagramm, das eine einfache Seite mit Bildern und Text zeigt, die vollständig auf dem Client in JavaScript gerendert wird. Da das Markup in JavaScript enthalten ist, kann der Preload-Scanner keine der Ressourcen erkennen. Alle Ressourcen werden zusätzlich verzögert, da JavaScript-Frameworks zusätzliche Netzwerk- und Verarbeitungszeit erfordern.
Abb. 14: Ein WebPageTest-Netzwerk-Wasserfalldiagramm einer clientseitig gerenderten Webseite, die in Chrome auf einem Mobilgerät über eine simulierte 3G-Verbindung ausgeführt wird. Da der Inhalt in JavaScript enthalten ist und für das Rendering ein Framework verwendet wird, ist die Bildressource im clientseitig gerenderten Markup für den Preload-Scanner nicht sichtbar. Die entsprechende serverseitig gerenderte Version ist in Abbildung 9 dargestellt.

Wenn Markup-Nutzlasten im Browser vollständig in JavaScript enthalten sind und von JavaScript gerendert werden, sind alle Ressourcen in diesem Markup für den Preload-Scanner unsichtbar. Dadurch wird die Erkennung wichtiger Ressourcen verzögert, was sich auf den LCP auswirkt. In diesen Beispielen wird die Anfrage für das LCP-Bild im Vergleich zu einer entsprechenden serverseitig gerenderten Seite, für die kein JavaScript erforderlich ist, erheblich verzögert.

Das weicht ein wenig vom Fokus dieses Artikels ab, aber die Auswirkungen des Renderns von Markup auf dem Client gehen weit über das Umgehen des Preload-Scanners hinaus. Zum einen führt die Einführung von JavaScript für eine Funktion, die es nicht erfordert, zu unnötiger Verarbeitungszeit, die sich auf die Interaction to Next Paint (INP) auswirken kann. Das Rendern extrem großer Mengen an Markup auf dem Client führt eher zu Long Tasks als die gleiche Menge an Markup, die vom Server gesendet wird. Der Grund dafür ist – abgesehen von der zusätzlichen Verarbeitung, die JavaScript erfordert –, dass Browser Markup vom Server streamen und das Rendering so aufteilen, dass lange Aufgaben tendenziell begrenzt werden. Clientseitig gerendertes Markup wird dagegen als eine einzige, monolithische Aufgabe behandelt, was sich auf den INP einer Seite auswirken kann.

Die Lösung für dieses Szenario hängt von der Antwort auf diese Frage ab: Gibt es einen Grund, warum das Markup Ihrer Seite nicht vom Server bereitgestellt werden kann, sondern auf dem Client gerendert werden muss? Wenn die Antwort „Nein“ lautet, sollten Sie nach Möglichkeit serverseitiges Rendering (SSR) oder statisch generiertes Markup in Betracht ziehen. So kann der Preload-Scanner wichtige Ressourcen vorab erkennen und abrufen.

Wenn Sie JavaScript benötigen, um bestimmten Teilen des Seiten-Markups Funktionen hinzuzufügen, können Sie dies auch mit SSR tun, entweder mit Vanilla JavaScript oder mit Hydration, um das Beste aus beiden Welten zu erhalten.

Den Preload-Scanner optimal nutzen

Der Preload-Scanner ist eine hocheffektive Browseroptimierung, mit der Seiten beim Start schneller geladen werden können. Wenn ihr Muster vermeidet, die die Fähigkeit des Browsers beeinträchtigen, wichtige Ressourcen im Voraus zu erkennen, vereinfacht ihr nicht nur die Entwicklung für euch selbst, sondern schafft auch eine bessere Nutzerfreundlichkeit, die in vielen Messwerten, einschließlich einiger Web Vitals, zu besseren Ergebnissen führt.

Hier noch einmal die wichtigsten Punkte aus diesem Beitrag:

  • Der Preload-Scanner des Browsers ist ein sekundärer HTML-Parser, der vor dem primären Parser scannt, wenn dieser blockiert ist, um Ressourcen zu finden, die früher abgerufen werden können.
  • Ressourcen, die nicht im Markup enthalten sind, das der Server bei der ersten Navigationsanfrage bereitstellt, können vom Preload-Scanner nicht gefunden werden. Der Preload-Scanner kann unter anderem durch Folgendes umgangen werden:
    • Ressourcen mit JavaScript in das DOM einfügen, seien es Skripts, Bilder, Stylesheets oder andere Elemente, die besser in der ursprünglichen Markup-Nutzlast vom Server enthalten sein sollten.
    • Lazy Loading von Bildern oder iFrames im „above the fold“-Bereich mithilfe einer JavaScript-Lösung.
    • Rendering von Markup auf dem Client, das mithilfe von JavaScript Verweise auf Dokumentunterressourcen enthalten kann.
  • Der Preload-Scanner scannt nur HTML. Der Inhalt anderer Ressourcen, insbesondere CSS, die Verweise auf wichtige Assets wie LCP-Kandidaten enthalten können, wird nicht untersucht.

Wenn Sie aus irgendeinem Grund ein Muster, das die Fähigkeit des Preload-Scanners zur Beschleunigung der Ladeleistung beeinträchtigt, nicht vermeiden können, sollten Sie den Ressourcenhinweis rel=preload in Betracht ziehen. Wenn du rel=preload verwendest, teste es in Labortools, um sicherzustellen, dass du den gewünschten Effekt erzielst. Laden Sie nicht zu viele Ressourcen vorab, denn wenn Sie alles priorisieren, wird nichts priorisiert.

Ressourcen