Hier erfahren Sie, was der Browser-Preload-Scanner ist, wie er die Leistung verbessert und wie Sie ihn aus dem Weg gehen können.
Ein oft übersehener Aspekt bei der Optimierung der Seitengeschwindigkeit ist das Wissen über die internen Abläufe von Browsern. Browser führen bestimmte Optimierungen durch, um die Leistung auf eine Weise zu verbessern, wie wir das als Entwickler nicht können – aber nur, solange diese Optimierungen nicht versehentlich verhindert werden.
Eine interne Browseroptimierung ist der Browser-Preload-Scanner. In diesem Beitrag erfahren Sie, wie der Preloader funktioniert und vor allem, wie Sie ihn nicht behindern.
Was ist ein Preloader-Scanner?
Jeder Browser hat einen primären HTML-Parser, der das Roh-Markup tokenisiert und in ein Objektmodell verarbeitet. Das geht so weiter, bis der Parser eine blockierende Ressource findet, z. B. ein Stylesheet, das mit einem <link>
-Element geladen wurde, oder ein Script, das mit einem <script>
-Element ohne async
- oder defer
-Attribut geladen wurde.
Bei CSS-Dateien wird das Rendering blockiert, um ein Flash of Unstyled Content (FOUC) zu verhindern. Dabei wird eine unformatierte Version einer Seite kurz angezeigt, bevor Stile darauf angewendet werden.
Der Browser blockiert auch das Parsen und Rendern der Seite, wenn er <script>
-Elemente ohne defer
- oder async
-Attribut findet.
Der Grund dafür ist, dass der Browser nicht sicher weiß, ob ein bestimmtes Script das DOM ändert, während der primäre HTML-Parser noch seine Arbeit erledigt. Aus diesem Grund wird der JavaScript-Code häufig am Ende des Dokuments geladen, damit die Auswirkungen eines blockierten Parsens und Renderns geringfügig werden.
Dies sind gute Gründe dafür, dass der Browser sowohl das Parsen als auch das Rendern blockieren sollte. Dennoch ist es nicht wünschenswert, einen dieser wichtigen Schritte zu blockieren, da sie die Show aufhalten können, indem sie die Erkennung anderer wichtiger Ressourcen verzögern. Glücklicherweise versuchen Browser, diese Probleme mithilfe eines sekundären HTML-Parsers namens Preloader zu minimieren.
Die Rolle eines Preloader-Scanners ist spekulativ. Das bedeutet, dass er das Roh-Markup untersucht, um Ressourcen zu finden, die opportunistisch abgerufen werden, bevor sie sonst vom primären HTML-Parser gefunden werden.
So erkennen Sie, ob der Scanner zum Vorabladen funktioniert
Der Prefetch-Scanner wird aufgrund blockierter Rendering- und Parsevorgänge verwendet. Wenn diese beiden Leistungsprobleme nicht auftreten würden, wäre der Preloader nicht sehr nützlich. Ob eine Webseite vom Preloader profitiert, hängt von diesen Blockierungsphänomenen ab. Dazu können Sie eine künstliche Verzögerung für Anfragen einführen, um herauszufinden, wo der Preloader funktioniert.
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, kommt es für das Stylesheet über einen Proxy-Dienst zu einer künstlichen Verzögerung von zwei Sekunden. Durch diese Verzögerung ist in der Netzwerkabfolge leichter zu erkennen, wo der Preloader-Scanner aktiv ist.
Wie Sie in der Ablaufgrafik sehen, erkennt der Vorab-Scanner das <img>
-Element auch dann, wenn das Rendering und das Parsen des Dokuments blockiert sind. Ohne diese Optimierung kann der Browser während der Blockierungsphase keine Elemente nach Bedarf abrufen und mehr Ressourcenanfragen wären aufeinanderfolgende anstatt gleichzeitig.
Nachdem wir uns dieses Beispiel angesehen haben, sehen wir uns einige reale Muster an, bei denen der Preloader-Scanner ausgetrickst werden kann, und was dagegen unternommen werden kann.
Eingeschleuste async
-Scripts
Angenommen, Ihr <head>
enthält HTML-Code mit Inline-JavaScript:
<script>
const scriptEl = document.createElement('script');
scriptEl.src = '/yall.min.js';
document.head.appendChild(scriptEl);
</script>
Eingebettete Scripts haben standardmäßig den Wert async
. Wenn dieses Script eingebettet wird, verhält es sich so, als wäre das Attribut async
darauf angewendet worden. Das bedeutet, dass sie 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:
Fassen wir zusammen, was passiert ist:
- Nach 0 Sekunden wird das Hauptdokument angefordert.
- Nach 1,4 Sekunden wird das erste Byte der Navigationsanfrage empfangen.
- Nach 2,0 Sekunden werden das CSS und das Bild angefordert.
- Da der Parser das Laden des Stylesheets blockiert und das Inline-JavaScript, das das
async
-Script einschleust, nach diesem Stylesheet mit 2,6 Sekunden kommt, sind die Funktionen des Scripts nicht so schnell verfügbar, wie sie es sein könnten.
Das ist nicht optimal, da die Anfrage für das Script erst erfolgt, nachdem der Download des Stylesheets abgeschlossen ist. Dadurch wird die Ausführung des Scripts verzögert. Da das <img>
-Element dagegen im vom Server bereitgestellten Markup sichtbar ist, wird es vom Preloader-Scanner erkannt.
Was passiert, wenn Sie ein reguläres <script>
-Tag mit dem async
-Attribut verwenden, anstatt das Script in das DOM einzuschleusen?
<script src="/yall.min.js" async></script>
Das Ergebnis:
Es kann verlockend sein, zu behaupten, dass diese Probleme mit rel=preload
behoben werden könnten. Das würde zwar funktionieren, kann aber einige Nebenwirkungen haben. Warum sollte man rel=preload
verwenden, um ein Problem zu beheben, das vermieden werden kann, indem man kein <script>
-Element in das DOM einschleust?
Durch das Vorladen wird das Problem hier zwar behoben, aber es entsteht ein neues: Das async
-Script 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 async
-Script vorab geladen wird, wird das Stylesheet weiterhin mit der Priorität „Höchste“ geladen, die Priorität des Scripts wurde jedoch auf „Hoch“ erhöht.
Wenn die Priorität einer Ressource erhöht wird, weist der Browser ihr mehr Bandbreite zu. Das bedeutet, dass die erhöhte Priorität des Scripts zu Bandbreitenkonflikten führen kann, auch wenn das Stylesheet die höchste Priorität hat. Das kann bei langsamen Verbindungen oder bei sehr großen Ressourcen ein Faktor sein.
Die Antwort ist einfach: Wenn ein Script beim Starten benötigt wird, sollten Sie den Preloader nicht durch Einschleusen in das DOM austricksen. Experimentieren Sie nach Bedarf mit der Platzierung von <script>
-Elementen sowie mit Attributen wie defer
und async
.
Lazy Loading mit JavaScript
Das Lazy-Loading ist eine gute Methode, um Daten zu sparen. Diese Methode wird häufig auf Bilder angewendet. Manchmal wird Lazy Loading jedoch fälschlicherweise auf Bilder angewendet, die sich sozusagen „above the fold“ befinden.
Dies kann zu potenziellen Problemen bei der Ressourcenerkennung durch den Preloader führen und die Zeit, die benötigt wird, um eine Referenz auf ein Bild zu finden, herunterzuladen, zu decodieren und anzuzeigen, unnötig verlängern. Sehen wir uns dieses Bild-Markup als Beispiel an:
<img data-src="/sand-wasp.jpg" alt="Sand Wasp" width="384" height="255">
Die Verwendung eines data-
-Präfixes ist ein gängiges Muster in JavaScript-basierten Lazy-Loadern. Wenn das Bild in den Viewport gescrollt wird, entfernt der Lazy-Loader das Präfix data-
. Im vorherigen Beispiel wird also data-src
zu src
. Dadurch 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 Vorabladescanner das data-src
-Attribut nicht auf dieselbe Weise liest wie ein src
- oder srcset
-Attribut, wird der Bildverweis nicht früher erkannt. Schlimmer noch: Das Laden des Bildes wird erst nach dem Herunterladen, Kompilieren und Ausführen des Lazy Loader-JavaScripts verzögert.
Je nach Größe des Bildes, was wiederum von der Größe des Darstellungsbereichs abhängen kann, kann es ein Element für Largest Contentful Paint (LCP) sein. Wenn der Preloader die Bildressource nicht spekulativ im Voraus abrufen kann – möglicherweise zu dem Zeitpunkt, an dem die Stylesheets der Seite das Rendering blockieren –, verschlechtert sich der LCP.
Die Lösung besteht darin, das Bild-Markup zu ändern:
<img src="/sand-wasp.jpg" alt="Sand Wasp" width="384" height="255">
Das ist das optimale Muster für Bilder, die sich beim Start im Viewport befinden, da der Preloader die Bildressource schneller erkennt und abruft.
In diesem vereinfachten Beispiel führt dies zu einer Verbesserung des LCP um 100 Millisekunden bei einer langsamen Verbindung. Das mag nicht nach einer großen Verbesserung aussehen, aber das ist es, wenn man bedenkt, dass es sich um eine schnelle Korrektur des Markups handelt und dass die meisten Webseiten komplexer sind als diese Beispiele. Das bedeutet, dass sich LCP-Kandidaten möglicherweise mit vielen anderen Ressourcen um die Bandbreite streiten müssen. Daher werden Optimierungen wie diese immer wichtiger.
CSS-Hintergrundbilder
Der Browser-Preload-Scanner scannt Markup. Andere Ressourcentypen wie CSS werden nicht gescannt. Das kann Abrufe von Bildern erfordern, auf die über die background-image
-Property verwiesen wird.
Wie HTML wird CSS von Browsern in ein eigenes Objektmodell verarbeitet, das als CSSOM bezeichnet wird. Wenn externe Ressourcen beim Erstellen des CSSOM erkannt werden, werden diese Ressourcen zum Zeitpunkt der Erkennung und nicht vom Preload-Scanner angefordert.
Angenommen, der LCP-Kandidat Ihrer Seite ist ein Element mit einer CSS-background-image
-Eigenschaft. Beim Laden von Ressourcen geschieht Folgendes:
In diesem Fall wird der Preloader nicht so sehr umgangen, sondern spielt keine Rolle. Wenn ein LCP-Kandidat auf der Seite jedoch aus einer background-image
-CSS-Property stammt, sollten Sie dieses Bild 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">
Diese rel=preload
-Hinweis ist klein, hilft dem Browser aber, das Bild früher zu finden als sonst:
Mit dem rel=preload
-Hinweis wird der LCP-Kandidat früher gefunden, wodurch sich die LCP-Zeit verkürzt. Dieser Hinweis hilft zwar, dieses Problem zu beheben, aber es ist möglicherweise besser, zu prüfen, ob Ihr LCP-Kandidat für Bilder aus CSS geladen werden muss. Mit einem <img>
-Tag haben Sie mehr Kontrolle über das Laden eines Bildes, das für den Darstellungsbereich geeignet ist, und ermöglichen gleichzeitig dem Preloader-Scanner, es zu finden.
Einfügen zu vieler Ressourcen
Dabei wird eine Ressource innerhalb des HTML-Codes platziert. Sie können Stylesheets in <style>
-Elemente, Scripts in <script>
-Elemente und praktisch jede andere Ressource mithilfe der Base64-Codierung inline einbinden.
Das Einfügen von Ressourcen kann schneller sein als das Herunterladen, da keine separate Anfrage für die Ressource gestellt wird. Sie befindet sich direkt im Dokument und wird sofort geladen. Es gibt jedoch erhebliche Nachteile:
- Wenn Sie Ihren HTML-Code nicht im Cache speichern und dies bei einer dynamischen HTML-Antwort einfach nicht möglich ist, werden die Inline-Ressourcen nie im Cache gespeichert. Das wirkt sich auf die Leistung aus, da die eingefügten Ressourcen nicht wiederverwendet werden können.
- Auch wenn Sie HTML im Cache speichern können, werden eingebettete Ressourcen nicht zwischen Dokumenten geteilt. Dies reduziert die Caching-Effizienz im Vergleich zu externen Dateien, die für einen gesamten Ursprung im Cache gespeichert und wiederverwendet werden können.
- Wenn Sie zu viel Inline-Code verwenden, verzögert sich das Erkennen von Ressourcen später im Dokument, da das Herunterladen dieser zusätzlichen Inline-Inhalte länger dauert.
Sehen Sie sich diese Seite als Beispiel an. Unter bestimmten Bedingungen ist das Bild oben auf der Seite der LCP-Kandidat und das CSS befindet sich in einer separaten Datei, die von einem <link>
-Element geladen wird. Die Seite verwendet außerdem vier Web-Schriftarten, die als separate Dateien von der CSS-Ressource angefordert werden.
Was passiert, wenn das CSS und alle Schriftarten als Base64-Ressourcen eingefügt werden?
Die Auswirkungen des Inline-Einfügens haben in diesem Beispiel negative Folgen für die LCP und die Leistung im Allgemeinen. Auf der Version der Seite, auf der nichts inline ist, wird das LCP-Bild in etwa 3,5 Sekunden dargestellt. Auf der Seite, auf der alles inline ist, wird das LCP-Bild erst nach etwas mehr als 7 Sekunden dargestellt.
Hier steckt noch mehr als nur der Scanner zum Vorabladen. Das Einbetten 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 erforderlich eingestuft werden. Wenn diese Schriftarten als base64-Schriftarten gekennzeichnet sind, werden sie heruntergeladen, unabhängig davon, ob sie für die aktuelle Seite erforderlich sind oder nicht.
Könnte ein Vorabladen die Situation verbessern? Sehr gern. Sie könnten das LCP-Bild vorab laden und die LCP-Zeit reduzieren. Wenn Ihr potenziell nicht im Cache speicherbarer HTML-Code jedoch mit Inline-Ressourcen überladen ist, hat dies andere negative Auswirkungen auf die Leistung. Auch der First Contentful Paint (FCP) ist von diesem Muster betroffen. In der Version der Seite, in der nichts eingebettet ist, beträgt die FCP etwa 2,7 Sekunden. In der Version, in der alles inline ist, beträgt die FCP etwa 5,8 Sekunden.
Seien Sie vorsichtig, wenn Sie Inhalte in HTML einfügen, insbesondere Base64-codierte Ressourcen. Im Allgemeinen wird dies abgesehen von sehr kleinen Ressourcen nicht empfohlen. So wenig wie möglich in die Halterung ein, denn zu viel Inline-Einbau spielt mit dem Feuer.
Markup mit clientseitigem JavaScript rendern
Zweifellos beeinflusst JavaScript die Seitengeschwindigkeit. Nicht nur Entwickler sind auf JavaScript angewiesen, um Interaktivität zu ermöglichen, sondern es gibt auch eine Tendenz, JavaScript für die Bereitstellung von Inhalten zu verwenden. Dies führt in gewisser Weise zu einer besseren Entwicklererfahrung. Vorteile für Entwickler bedeuten jedoch nicht immer Vorteile für Nutzer.
Ein Muster, das den Vorabladescanner verhindern kann, ist das Rendern des Markups mit clientseitigem JavaScript:
Wenn Markup-Nutzlast in JavaScript im Browser enthalten und vollständig von JavaScript gerendert wird, sind alle Ressourcen in diesem Markup für den Preloader unsichtbar. Dadurch wird die Erkennung wichtiger Ressourcen verzögert, was sich auf die LCP auswirkt. In diesen Beispielen ist die Anfrage nach dem LCP-Bild im Vergleich zur entsprechenden vom Server gerenderten Darstellung, für die kein JavaScript erforderlich ist, erheblich verzögert.
Das weicht ein wenig vom Schwerpunkt dieses Artikels ab, aber die Auswirkungen des Markup-Renderings auf dem Client gehen weit über das Verhindern des Preloading-Scanners hinaus. Zum einen führt die Einführung von JavaScript für eine Funktion, für die es nicht erforderlich ist, zu unnötigen Verarbeitungszeiten, die sich auf die Interaktion bis zur nächsten Darstellung (Interaction to Next Paint, INP) auswirken können. Beim Rendern extrem großer Mengen an Markup auf dem Client ist die Wahrscheinlichkeit höher, dass lange Aufgaben generiert werden, als wenn dieselbe Menge an Markup vom Server gesendet wird. Der Grund hierfür – abgesehen von der zusätzlichen Verarbeitung durch JavaScript – besteht darin, dass Browser Markups vom Server streamen und das Rendering so aufteilen, dass lange Aufgaben tendenziell eingeschränkt werden. Clientseitig gerendertes Markup wird dagegen als einzelne, 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 Preloader wichtige Ressourcen im Voraus finden und bei Bedarf abrufen.
Wenn für Ihre Seite JavaScript erforderlich ist, um einigen 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 nutzen.
Dem Preloader-Scanner helfen, Ihnen zu helfen
Der Preload-Scanner ist eine äußerst effektive Browser-Optimierung, mit der Seiten beim Start schneller geladen werden. Wenn Sie Muster vermeiden, die es verhindern, wichtige Ressourcen im Voraus zu ermitteln, erleichtern Sie sich nicht nur die Entwicklung, sondern verbessern auch die Nutzererfahrung und erzielen bessere Ergebnisse bei vielen Messwerten, einschließlich einiger Web Vitals.
Hier noch einmal die wichtigsten Punkte aus diesem Beitrag:
- Der Browser-Preload-Scanner 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 vorhanden sind, das der Server bei der ersten Navigationsanfrage zur Verfügung stellt, können vom Pre-Load-Scanner nicht gefunden werden. Beispiele für Möglichkeiten, den Preloader-Scanner zu umgehen:
- Ressourcen mit JavaScript in das DOM einschleusen, z. B. Scripts, Bilder, Stylesheets oder andere Elemente, die besser in der ursprünglichen Markup-Nutzlast vom Server enthalten wären.
- Lazy Loading von Bildern oder iFrames im Above-the-Fold-Bereich mit einer JavaScript-Lösung
- Rendern von Markup auf dem Client, das Verweise auf Dokument-Unterressourcen mithilfe von JavaScript enthalten kann.
- Der Scanner für Vorabladevorgänge scannt nur HTML. Es werden nicht die Inhalte anderer Ressourcen, insbesondere CSS, untersucht, die Verweise auf wichtige Assets enthalten können, einschließlich LCP-Kandidaten.
Wenn Sie aus irgendeinem Grund kein Muster vermeiden können, das die Fähigkeit des Preloader-Scanners, die Ladeleistung zu beschleunigen, negativ beeinträchtigt, sollten Sie den Ressourcenhinweis rel=preload
berücksichtigen. Wenn Sie rel=preload
verwenden, sollten Sie mithilfe von Lab-Tools prüfen, ob Sie den gewünschten Effekt erzielen. Laden Sie nicht zu viele Ressourcen vorab, denn wenn Sie alles priorisieren, wird nichts priorisiert.
Ressourcen
- Asynchrone Skripts mit Inline-Skripts als schädlich eingestuft
- So sorgen Browser-Preloader für schnelleres Laden von Seiten
- Wichtige Assets vorab laden, um die Ladegeschwindigkeit zu verbessern
- Netzwerkverbindungen frühzeitig herstellen, um die wahrgenommene Seitengeschwindigkeit zu verbessern
- Largest Contentful Paint optimieren
Hero-Image von Unsplash, von Mohammad Rahmani