Finden Sie heraus, was der Vorablade-Scanner im Browser ist, wie er die Leistung verbessert und wie Sie ihn vermeiden können.
Ein Aspekt der Optimierung der Seitengeschwindigkeit, der bisher übersehen wurde, besteht darin, sich mit den internen Strukturen des Browsers vertraut zu machen. Browser nehmen bestimmte Optimierungen vor, um die Leistung auf eine Weise zu verbessern, die wir als Entwickler nicht können – aber nur, solange diese Optimierungen nicht unbeabsichtigt verhindert werden.
Eine interne Browseroptimierung, die Sie verstehen sollten, ist der Browser Preload Scanner. In diesem Beitrag erfahren Sie, wie der Vorablade-Scanner funktioniert und, was noch wichtiger ist, wie Sie vermeiden können, ihn zu stören.
Was ist ein Vorabladescanner?
Jeder Browser verfügt über einen primären HTML-Parser, der Roh-Markup tokenisiert und in ein Objektmodell verarbeitet. Das Ganze wird so lange fortgesetzt, bis der Parser eine Pause macht, wenn er eine blockierende Ressource findet, z. B. ein Stylesheet mit einem <link>
-Element oder ein Script, das mit einem <script>
-Element ohne async
- oder defer
-Attribut geladen wurde.
Bei CSS-Dateien wird sowohl das Parsen als auch das Rendering blockiert, um ein Flash of Unstyled Content (FOUC) zu verhindern. Das ist der Fall, wenn kurz eine Version einer Seite ohne Stil zu sehen ist, bevor Stile darauf angewendet werden.
Der Browser blockiert außerdem das Parsen und Rendern der Seite, wenn <script>
-Elemente ohne defer
- oder async
-Attribut erkannt werden.
Dies liegt daran, dass der Browser nicht sicher wissen kann, ob ein bestimmtes Skript das DOM ändert, während der primäre HTML-Parser noch seine Aufgabe ausführt. Aus diesem Grund ist es üblich, JavaScript am Ende des Dokuments zu laden, sodass die Auswirkungen des blockierten Parsings und des Renderings marginal sind.
Dies sind gute Gründe dafür, warum der Browser sowohl das Parsing als auch das Rendering blockieren sollte. Allerdings ist es nicht wünschenswert, diese wichtigen Schritte zu blockieren, da sie die Sendung anhalten können, da sie das Erkennen anderer wichtiger Ressourcen verzögern. Glücklicherweise tun Browser ihr Möglichstes, um diese Probleme mit einem sekundären HTML-Parser, dem sogenannten Preload Scanner, zu entschärfen.
Die Rolle eines Vorladescanners ist spekulativ. Das bedeutet, dass er unbearbeitetes Markup untersucht, um Ressourcen zu finden, die opportunistisch abgerufen werden können, bevor der primäre HTML-Parser sie ansonsten entdecken würde.
So erkennen Sie, ob der Vorabladescanner funktioniert
Der Vorabladescanner ist vorhanden, weil Rendering und Parsen blockiert sind. Wenn es diese beiden Leistungsprobleme nie gäbe, wäre der Vorabladescanner nicht sehr nützlich. Der Schlüssel, um herauszufinden, ob eine Webseite vom Preload-Scanner profitiert, hängt von diesen Blockierphänomenen ab. Dazu können Sie eine künstliche Verzögerung für Anfragen einführen, um herauszufinden, wo der Vorabladescanner funktioniert.
Nehmen Sie diese Seite mit einfachem Text und Bildern mit einem Stylesheet als Beispiel. Da CSS-Dateien sowohl das Rendering als auch das Parsen blockieren, wird durch einen Proxy-Dienst eine künstliche Verzögerung von zwei Sekunden für das Stylesheet eingeführt. Durch diese Verzögerung lässt sich in der Netzwerkabfolge leichter erkennen, wo der Vorladescanner arbeitet.
Wie Sie an dem Wasserfall sehen können, erkennt der Vorabladescanner das <img>
-Element, auch wenn das Rendern und das Parsen von Dokumenten blockiert sind. Ohne diese Optimierung kann der Browser während des Sperrzeitraums keine opportunistischen Daten abrufen und mehr Ressourcenanfragen würden aufeinanderfolgende und nicht gleichzeitig ausgeführt.
Nachdem das Spielzeugbeispiel aus dem Weg gerissen ist, schauen wir uns einige reale Muster an, bei denen der Vorladescanner abgewehrt werden kann und wie wir diese beheben können.
async
Scripts eingefügt
Angenommen, Ihre Datei <head>
enthält HTML-Code, der Inline-JavaScript enthält, z. B.:
<script>
const scriptEl = document.createElement('script');
scriptEl.src = '/yall.min.js';
document.head.appendChild(scriptEl);
</script>
Injizierte Scripts haben standardmäßig den Wert async
. Wenn dieses Script eingeschleust wird, verhält es sich also so, als wäre das Attribut async
darauf angewendet. 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>
auf ein <link>
-Element folgt, das eine externe CSS-Datei lädt, erhalten Sie ein suboptimales Ergebnis:
Schauen wir uns an, was passiert ist:
- Bei 0 Sekunden wird das Hauptdokument angefordert.
- Bei 1, 4 Sekunden kommt das erste Byte der Navigationsanfrage an.
- 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
-Skript einschleust, nach diesem Stylesheet nach 2,6 Sekunden erscheint, ist die von diesem Skript bereitgestellte Funktion nicht so schnell verfügbar, wie es möglich wäre.
Dies ist nicht optimal, da die Anforderung für das Skript erst erfolgt, nachdem das Stylesheet heruntergeladen wurde. Dadurch wird die Ausführung des Skripts verzögert. Da das <img>
-Element hingegen im vom Server bereitgestellten Markup sichtbar ist, wird es vom Vorabladescanner erkannt.
Was passiert also, wenn Sie ein reguläres <script>
-Tag mit dem async
-Attribut verwenden, anstatt das Skript in das DOM zu injizieren?
<script src="/yall.min.js" async></script>
Das ist das Ergebnis:
Es besteht die Versuchung, diese Probleme mit rel=preload
zu beheben. Das würde zwar funktionieren, aber es kann einige Nebenwirkungen haben. Warum sollten Sie rel=preload
verwenden, um ein Problem zu beheben, das sich vermeiden lässt, wenn Sie ein <script>
-Element nicht in das DOM einschleusen?
Das Vorabladen "behebt" das Problem hier, bringt jedoch ein neues Problem mit sich: Das Skript async
in den ersten beiden Demos wird – obwohl es in <head>
geladen wurde – mit der Priorität "Niedrig" geladen, während das Stylesheet mit der Priorität "Höchste" geladen wird. In der letzten Demo, bei der das async
-Skript vorab geladen wurde, wird das Stylesheet weiterhin mit der höchsten Priorität geladen, die Priorität des Skripts wurde jedoch auf "Hoch" hochgestuft.
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 Skripts zu Bandbreitenkonflikten führen kann, obwohl das Stylesheet die höchste Priorität hat. Das kann ein Faktor bei langsamen Verbindungen oder bei sehr großen Ressourcen sein.
Die Antwort ist ganz einfach: Wenn beim Start ein Skript benötigt wird, muss der Vorladescanner nicht durch eine Injektion in das DOM deaktiviert werden. Experimentieren Sie bei Bedarf mit der Platzierung des <script>
-Elements sowie mit Attributen wie defer
und async
.
Lazy Loading mit JavaScript
Lazy Loading ist eine sehr gute Methode zur Schonung von Daten, die häufig auf Bilder angewendet wird. Manchmal wird Lazy Loading jedoch auf Bilder angewendet, die sozusagen „above the fold“ (ohne Scrollen sichtbar) sind.
Dies führt im Zusammenhang mit dem Vorabladescanner zu Problemen mit der Auffindbarkeit von Ressourcen und kann unnötig lange dauern, bis eine Referenz zu einem Bild erkannt, heruntergeladen, decodiert und präsentiert wird. Nehmen wir als Beispiel dieses Bild-Markup:
<img data-src="/sand-wasp.jpg" alt="Sand Wasp" width="384" height="255">
Die Verwendung des Präfixes data-
ist ein gängiges Muster in Lazy Loading mit JavaScript. 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
. Bei dieser Aktualisierung wird der Browser aufgefordert, die Ressource abzurufen.
Dieses Muster ist erst problematisch, wenn es auf Bilder angewendet wird, die sich beim Start im Darstellungsbereich befinden. Da der Scanner zum Vorabladen das Attribut data-src
nicht auf dieselbe Weise liest wie ein Attribut src
(oder srcset
), wurde die Bildreferenz nicht früher gefunden. Schlimmer noch, das Laden des Bildes wird erst nach dem Herunterladen, Kompilieren und Ausführen von JavaScript durch Lazy Loader verzögert.
Je nach Größe des Bildes – sie kann auch von der Größe des Darstellungsbereichs abhängen – kann es ein mögliches Element für Largest Contentful Paint (LCP) sein. Wenn der Scanner zum Vorabladen die Bildressource nicht im Voraus spekulativ abrufen kann – möglicherweise während des Zeitraums, an dem das Rendering des Stylesheets der Seite blockiert wird –, 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 Vorabladescanner die Bildressource erkennt und schneller abrufen kann.
Das Ergebnis in diesem vereinfachten Beispiel ist eine Verbesserung des LCP bei langsamen Verbindungen um 100 Millisekunden. Dies mag nicht nach einer großen Verbesserung erscheinen, aber wenn man bedenkt, dass die Lösung eine schnelle Markup-Korrektur ist und dass die meisten Webseiten komplexer sind als diese Beispiele. Das bedeutet, dass LCP-Kandidaten unter Umständen mit vielen anderen Ressourcen um Bandbreite kämpfen müssen, sodass Optimierungen wie diese immer wichtiger werden.
CSS-Hintergrundbilder
Denken Sie daran, dass der Scanner für das Vorabladen des Browsers das Markup scannt. Andere Ressourcentypen wie CSS werden nicht gescannt. Für Bilder, auf die durch die background-image
-Property verwiesen wird, werden möglicherweise Bilder abgerufen.
Wie HTML verarbeiten Browser CSS in einem eigenen Objektmodell, das als CSSOM bezeichnet wird. Wenn beim Erstellen des CSSOMs externe Ressourcen erkannt werden, werden diese Ressourcen zum Zeitpunkt der Erkennung angefordert und nicht vom Vorabladescanner.
Angenommen, der LCP-Kandidaten Ihrer Seite ist ein Element mit der CSS-Eigenschaft background-image
. Beim Laden der Ressourcen geschieht Folgendes:
In diesem Fall ist der Vorladescanner nicht so stark besiegt, wie er unbeteiligt ist. Wenn ein LCP-Kandidaten auf der Seite jedoch aus einer background-image
-CSS-Eigenschaft stammt, sollte das Bild vorab geladen werden:
<!-- 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">
Dieser rel=preload
-Hinweis ist zwar klein, hilft dem Browser jedoch, das Bild schneller zu finden, als er es sonst tun würde:
Mit dem Hinweis rel=preload
wird der LCP-Kandidaten früher erkannt, wodurch die LCP-Zeit verringert wird. Mit diesem Hinweis lässt sich das Problem zwar beheben. Besser ist es aber vielleicht herauszufinden, ob der LCP-Kandidaten für das Bild über 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 gleichzeitig zulassen, dass der Vorabladescanner es erkennt.
Zu viele Ressourcen inline einbinden
Inline ist eine Praxis, bei der eine Ressource innerhalb des HTML-Codes platziert wird. Mit der Base64-Codierung können Sie Stylesheets in <style>
-Elementen, Skripts in <script>
-Elementen und praktisch jeder anderen Ressource einfügen.
Das Inline-Inline-raten von Ressourcen kann schneller sein als das Herunterladen, da keine separate Anfrage für die Ressource ausgegeben 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 nicht möglich ist –, werden die Inline-Ressourcen nie im Cache gespeichert. Dies 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 den Dokumenten geteilt. Dies reduziert die Caching-Effizienz im Vergleich zu externen Dateien, die im Cache gespeichert und für den gesamten Ursprung wiederverwendet werden können.
- Wenn Sie zu viele Inline-Inhalte inline einfügen, verzögert der Vorablade-Scanner später im Dokument Ressourcen, da der Download dieser zusätzlichen Inline-Inhalte länger dauert.
Sehen wir uns diese Seite als Beispiel an. Unter bestimmten Bedingungen ist der LCP-Kandidat das Bild oben auf der Seite und der CSS-Code 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 der CSS-Code und alle Schriftarten als base64-Ressourcen angegeben sind?
Die Auswirkungen des Inline-Objekts haben in diesem Beispiel negative Auswirkungen auf den LCP – und auf die Leistung im Allgemeinen. Die Version der Seite, die keine Inline-Elemente enthält, malt das LCP-Bild in etwa 3,5 Sekunden. Auf der Seite, auf der alles inline platziert wird, erscheint das LCP-Bild erst nach etwas mehr als sieben Sekunden.
Hier kann mehr als nur der Vorlade-Scanner gespielt werden. Die Inline-Schriftarten ist keine gute Strategie, da base64 ein ineffizientes Format für Binärressourcen ist. Ein weiterer Faktor, der eine Rolle spielt, ist, dass externe Schriftartenressourcen nur heruntergeladen werden, wenn dies durch CSSOM als erforderlich erachtet wird. Wenn diese Schriftarten als Base64-konform angegeben sind, werden sie heruntergeladen, unabhängig davon, ob sie für die aktuelle Seite benötigt werden oder nicht.
Könnte ein Vorabladen die Dinge hier verbessern? Sehr gern. Sie könnten das LCP-Bild vorab laden und die LCP-Zeit reduzieren. Wenn Sie jedoch potenziell nicht im Cache speicherbaren HTML-Code mit Inline-Ressourcen aufblähen, hat dies andere negative Auswirkungen auf die Leistung. Auch First Contentful Paint (FCP) ist von diesem Muster betroffen. In der Version der Seite, die keine Inline enthält, beträgt FCP etwa 2,7 Sekunden. In der Version, in der alles enthalten ist, dauert FCP etwa 5,8 Sekunden.
Seien Sie sehr vorsichtig, wenn Sie Elemente in HTML einfügen, insbesondere base64-codierte Ressourcen. Im Allgemeinen wird es nicht empfohlen, mit Ausnahme von sehr kleinen Ressourcen. So wenig wie möglich inline, denn zu viel wird mit dem Feuer gespielt.
Markup mit clientseitigem JavaScript rendern
Es gibt keinen Zweifel: JavaScript wirkt sich definitiv auf die Seitengeschwindigkeit aus. Entwickler verlassen sich nicht nur darauf, wenn sie für Interaktivität sorgen, sondern sie nutzen sie auch für die Bereitstellung von Inhalten selbst. Dies führt in vielerlei Hinsicht zu einer besseren Erfahrung für Entwickler, die jedoch nicht immer auch für die Nutzer gleich ist.
Ein Muster, das den Preload-Scanner umgehen kann, ist das Rendern von Markup mit clientseitigem JavaScript:
Wenn Markup-Nutzlasten vollständig in JavaScript im Browser enthalten sind und von diesem gerendert werden, sind alle Ressourcen in diesem Markup für den Vorabladescanner praktisch unsichtbar. Dadurch wird das Erkennen wichtiger Ressourcen verzögert, was sich sicherlich auf den LCP auswirkt. In diesen Beispielen ist die Anfrage für das LCP-Bild im Vergleich zur entsprechenden vom Server gerenderten Version, für die kein JavaScript erforderlich ist, deutlich verzögert.
Dies weicht etwas vom Schwerpunkt dieses Artikels ab, aber die Auswirkungen des Renderings von Markups auf den Client gehen weit über das Verhindern des Vorabladescanners hinaus. Zum einen führt die Einführung von JavaScript zu einer unnötigen Verarbeitungszeit, die sich auf Interaction to Next Paint (INP) auswirken kann.
Außerdem führt das Rendern extrem großer Markup-Mengen auf dem Client mit höherer Wahrscheinlichkeit zu langen Aufgaben als die gleiche Menge an Markup, die vom Server gesendet wird. Abgesehen von der zusätzlichen Verarbeitung durch JavaScript ist der Grund dafür, dass Browser die Markups vom Server streamen und das Rendering so aufteilen, dass lange Aufgaben vermieden werden. Vom Client gerenderte Markups hingegen werden als einzelne, monolithische Aufgabe verarbeitet, die neben INP auch die Messwerte für die Reaktionszeit von Seiten wie Total Blocking Time (TBT) oder First Input Delay (FID) beeinflussen kann.
Die Abhilfe 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 und auf dem Client gerendert werden kann? Wenn die Antwort „Nein“ lautet, sollten Sie nach Möglichkeit serverseitiges Rendering (SSR) oder statisch generiertes Markup in Betracht ziehen, da der Vorladescanner so wichtige Ressourcen im Voraus erkennen und opportunistisch abrufen kann.
Wenn Ihre Seite JavaScript benötigt, um Funktionen an einige Teile des Markups Ihrer Seite anzufügen, können Sie dies mit SSR entweder mit einfachem JavaScript oder mit der Hydration tun, um das Beste aus beiden Welten herauszuholen.
Helfen Sie dem Vorablade-Scanner dabei,
Der Preload-Scanner ist eine äußerst effektive Browser-Optimierung, mit der die Seiten während des Startvorgangs schneller geladen werden. Indem Sie Muster vermeiden, die es daran hindern, wichtige Ressourcen im Voraus zu finden, erleichtern Sie sich nicht nur die Entwicklung für sich selbst. Sie schaffen auch bessere Nutzererfahrungen, die zu besseren Ergebnissen bei vielen Messwerten führen, einschließlich einiger Web Vitals.
Hier noch einmal die folgenden Punkte, die Sie aus diesem Post mitnehmen sollten:
- Der Browser Preload Scanner ist ein sekundärer HTML-Parser, der vor dem primären Parser sucht, wenn dieser blockiert ist, um Ressourcen zu finden, die er früher abrufen kann.
- Ressourcen, die nicht in dem vom Server bei der ersten Navigationsanfrage bereitgestellten Markup vorhanden sind, können vom Vorladescanner nicht erkannt werden. So kann der Vorabladescanner unter anderem blockiert werden:
- Einfügen von Ressourcen in das DOM mit JavaScript, z. B. Skripte, Bilder, Stylesheets oder etwas anderes, das in der anfänglichen Markup-Nutzlast vom Server besser geeignet wäre.
- Lazy Loading von Bildern oder iFrames, die ohne Scrollen sichtbar sind, mithilfe einer JavaScript-Lösung
- Rendering von Markup auf dem Client, das Verweise auf Dokument-Unterressourcen mit JavaScript enthalten kann
- Der Vorabladescanner scannt nur HTML. Es prüft nicht die Inhalte anderer Ressourcen, insbesondere von CSS, die möglicherweise Verweise auf wichtige Assets wie LCP-Kandidaten enthalten.
Wenn Sie aus irgendeinem Grund ein Muster nicht vermeiden können, das sich negativ auf die Fähigkeit des Vorladescanners zur Beschleunigung der Ladeleistung auswirkt, beachten Sie den Ressourcenhinweis rel=preload
. Wenn Sie rel=preload
doch verwenden, testen Sie in Lab-Tools, ob Sie damit den gewünschten Effekt erzielen. Zu guter Letzt sollten Sie nicht zu viele Ressourcen vorab laden, denn wenn Sie alles priorisieren, passiert nichts.
Ressourcen
- Asynchrone Skripts mit Inline-Skripts gelten als schädlich
- Wie der Browser-Preloader sorgt dafür, dass Seiten schneller geladen werden
- Wichtige Assets vorab laden, um die Ladegeschwindigkeit zu verbessern
- Stellen Sie frühzeitig Netzwerkverbindungen her, um die wahrgenommene Seitengeschwindigkeit zu verbessern.
- Largest Contentful Paint optimieren
Hero-Image aus Unsplash von Mohammad Rahmani .