Hier erfahren Sie, was der Browser Preload Scanner ist, wie er die Leistung verbessert und wie Sie ihm aus dem Weg gehen können.
Ein Aspekt bei der Optimierung der Seitengeschwindigkeit ist es, sich mit den internen Strukturen von Browsern vertraut zu machen. 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, die Sie kennen sollten, ist der Browser-Preload-Scanner. In diesem Beitrag erfahren Sie, wie der Scanner zum Vorabladen funktioniert – und, noch wichtiger, wie Sie verhindern können, dass er in die Quere kommt.
Was ist ein Preload-Scanner?
Jeder Browser hat einen primären HTML-Parser, der unbearbeitetes Markup tokenisiert und in ein Objektmodell verarbeitet. Das alles geht fröhlich weiter, bis der Parser eine blockierende Ressource findet, z. B. ein Stylesheet, das mit einem <link>
-Element geladen ist, 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 <script>
-Elemente ohne defer
- oder async
-Attribut gefunden werden.
Der Grund dafür ist, dass der Browser nicht mit Sicherheit wissen kann, ob ein bestimmtes Skript das DOM ändert, während der primäre HTML-Parser noch seine Aufgabe ausführt. 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.
Das sind gute Gründe dafür, dass der Browser sowohl das Parsen als auch das Rendern blockieren sollte. Es ist jedoch nicht wünschenswert, einen dieser wichtigen Schritte zu blockieren, da dies die Suche nach anderen wichtigen Ressourcen verzögern kann. Glücklicherweise versuchen die Browser, diese Probleme mithilfe eines sekundären HTML-Parsers, dem Preload Scanner, zu minimieren.
Die Rolle eines Preload-Scanners ist spekulativ. Das bedeutet, dass er Roh-Markup untersucht, um Ressourcen zu finden, die opportunistisch abgerufen werden können, bevor der primäre HTML-Parser sie sonst finden würde.
Prüfen, ob der Preloader funktioniert
Der Preload-Scanner ist vorhanden, weil das Rendering und Parsen blockiert sind. Wenn diese beiden Leistungsprobleme nie existieren würden, wäre der Scanner zum Vorabladen nicht sehr nützlich. 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 einleiten, um herauszufinden, wo der Scanner zum Vorabladen arbeitet.
Sehen Sie sich diese Seite mit einfachem Text und Bildern mit einem Stylesheet als Beispiel an. Da CSS-Dateien sowohl das Rendern 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 es in der Netzwerkabfolge einfacher zu erkennen, wo der Preload-Scanner arbeitet.
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 Möglichkeit abrufen und es würden mehr Ressourcenanfragen nacheinander und nicht gleichzeitig ausgeführt.
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.
async
Script eingefügt
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>
Eingefügte Skripts sind standardmäßig async
. Wenn dieses Script also eingeschleust wird, verhält es sich so, als würde das async
-Attribut darauf angewendet werden. 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 nicht optimales 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 Sekunden werden das CSS und das Bild angefordert.
- Da der Parser das Laden des Stylesheets blockiert und das Inline-JavaScript, das das Skript
async
einfügt, kommt nach diesem Stylesheet nach 2,6 Sekunden, sodass die von diesem Skript bereitgestellten Funktionen nicht so schnell wie möglich verfügbar sind.
Dies ist nicht optimal, da die Anfrage für das Skript erst erfolgt, nachdem der Download des Stylesheets abgeschlossen ist. Dadurch wird die Ausführung des Scripts verzögert. Da das <img>
-Element im Gegensatz dazu im vom Server bereitgestellten Markup sichtbar ist, wird es vom Preload-Scanner erkannt.
Was passiert, wenn Sie ein reguläres <script>
-Tag mit dem Attribut async
verwenden, anstatt das Skript in das DOM einzufügen?
<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 sicher funktionieren, kann aber einige Nebenwirkungen mit sich bringen. Warum sollten Sie schließlich rel=preload
verwenden, um ein Problem zu beheben, das sich vermeiden lässt, indem Sie kein <script>
-Element in das DOM einschleusen?
„Korrekturen“ vorab laden hier tritt das Problem auf, führt jedoch zu einem neuen Problem: Das async
-Skript in den ersten beiden Demos wird – obwohl es im <head>
geladen wurde – mit einem niedrigen Wert geladen. Priorität hat, während das Stylesheet mit der höchsten Priorität geladen wird. Priorität haben. In der letzten Demo, in der das Skript async
vorab geladen wurde, wird das Stylesheet immer noch mit der höchsten Einstellung geladen. aber die Priorität des Skripts wurde auf "Hoch" hochgestuft.
Wenn die Priorität einer Ressource erhöht wird, weist der Browser ihr mehr Bandbreite zu. Das bedeutet, dass die höhere Priorität des Skripts zu Bandbreitenkonflikten führen kann, auch wenn das Stylesheet die höchste Priorität hat. Dies kann bei langsamen Verbindungen oder bei sehr großen Ressourcen der Grund sein.
Die Antwort ist ganz einfach: Wenn beim Start ein Skript benötigt wird, sollten Sie den Preload-Scanner nicht durch Einschleusen in das DOM außer Kraft setzen. Experimentieren Sie bei Bedarf mit der Platzierung von <script>
-Elementen sowie mit Attributen wie defer
und async
.
Lazy Loading mit JavaScript
Lazy Loading ist eine hervorragende Methode, um Daten zu sparen, die oft auf Bilder angewendet wird. Manchmal wird Lazy Loading jedoch fälschlicherweise auf Bilder angewendet, die sich sozusagen „above the fold“ befinden.
Dies birgt potenzielle Probleme bei der Auffindbarkeit von Ressourcen, wenn es um den Vorabladescanner geht. Außerdem kann es unnötig lange dauern, bis ein Verweis auf ein Bild erkannt, heruntergeladen, decodiert und präsentiert wird. Sehen wir uns dieses Bild-Markup als Beispiel 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 in JavaScript-gestützten Lazy Loader. Wenn in den Darstellungsbereich des Bildes gescrollt wird, entfernt der Lazy Loader das Präfix data-
. Das bedeutet, dass im vorherigen Beispiel data-src
zu src
wird. 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, die auch von der Größe des Darstellungsbereichs abhängen kann, kann es ein Kandidatenelement für den 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">
Dies ist das optimale Muster für Bilder, die sich beim Start im Darstellungsbereich befinden, da der Scanner zum Vorabladen die Bildressource schneller erkennt und abruft.
Das Ergebnis in diesem vereinfachten Beispiel ist eine Verbesserung des LCP um 100 Millisekunden bei einer langsamen Verbindung. Dies mag nicht nach einer großen Verbesserung erscheinen, aber wenn man bedenkt, dass es sich bei der Lösung um eine schnelle Markup-Korrektur handelt und dass die meisten Webseiten komplexer sind als diese Beispiele. Das bedeutet, dass LCP-Kandidaten möglicherweise mit vielen anderen Ressourcen um Bandbreite konkurrieren 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. Dabei werden möglicherweise Bilder abgerufen, auf die das Attribut background-image
verweist.
Wie HTML wird CSS von Browsern in ein eigenes Objektmodell verarbeitet, das als CSSOM bezeichnet wird. Wenn beim Erstellen des CSSOM externe Ressourcen gefunden werden, werden diese Ressourcen zum Zeitpunkt der Erkennung angefordert und nicht vom Pre-Load-Scanner.
Der LCP-Kandidat Ihrer Seite ist ein Element mit der CSS-Eigenschaft background-image
. Wenn Ressourcen geladen werden, geschieht Folgendes:
In diesem Fall ist der Scanner für das Vorabladen nicht so stark besiegt, sondern ohne Beteiligung. Wenn ein LCP-Kandidat auf der Seite jedoch von einer CSS-Eigenschaft background-image
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 Hinweis rel=preload
wird der LCP-Kandidat früher erkannt, wodurch die LCP-Zeit verringert wird. Mit diesem Hinweis lässt sich das Problem zwar beheben, aber vielleicht ist es besser, zu prüfen, ob der LCP-Kandidat für ein Bild aus CSS geladen werden muss. Mit einem <img>
-Tag haben Sie mehr Kontrolle über das Laden eines Bilds, das für den Darstellungsbereich geeignet ist, und ermöglichen dem vorab ladenden Scanner, es zu erkennen.
Einfügen zu vieler Ressourcen
Dabei wird eine Ressource innerhalb des HTML-Codes platziert. Mithilfe der Base64-Codierung können Sie Stylesheets in <style>
-Elementen, Skripts in <script>
-Elementen und praktisch jeder anderen Ressource inline einfügen.
Das Inlinen von Ressourcen kann schneller sein als das Herunterladen, da keine separate Anfrage für die Ressource ausgegeben wird. Sie wird direkt im Dokument angezeigt und 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. 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 Dokumenten gemeinsam genutzt. Dies reduziert die Caching-Effizienz im Vergleich zu externen Dateien, die im Cache gespeichert und im gesamten Ursprung wiederverwendet werden können.
- Wenn Sie zu viele Inline-Inhalte inline einfügen, verzögert sich der Vorabladen des Scanners, sodass er später im Dokument keine weiteren Ressourcen findet, 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. Die Seite verwendet außerdem vier Webschriftarten, die als separate Dateien von der CSS-Ressource angefordert werden.
Was passiert jetzt, wenn die CSS und alle Schriftarten als base64-Ressourcen inline eingefügt werden?
Die Auswirkung einer Inline-Anzeige hat in diesem Beispiel negative Auswirkungen auf den LCP – und auf die Leistung im Allgemeinen. Die Version der Seite, die keine Inline-Elemente enthält, zeichnet das LCP-Bild nach etwa 3,5 Sekunden auf. Die Seite, auf der alles inline angezeigt wird, zeichnet das LCP-Bild erst etwas mehr als 7 Sekunden auf.
Hier steckt noch mehr als nur der Scanner zum Vorabladen. Das Inline-Format von Schriftarten ist keine gute Strategie, da base64 ein ineffizientes Format für binäre Ressourcen ist. Ein weiterer Faktor ist, dass externe Schriftartenressourcen nur dann heruntergeladen werden, wenn dies vom CSSOM als notwendig erachtet wird. 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 hier Verbesserungen bewirken? Sehr gern. Sie könnten das LCP-Bild vorab laden und die LCP-Zeit verkürzen. Das Aufblähen Ihres potenziell nicht im Cache ablegbaren HTML-Codes mit Inline-Ressourcen hat jedoch andere negative Auswirkungen auf die Leistung. Von diesem Muster ist auch First Contentful Paint (FCP) betroffen. In der Version der Seite, in der nichts eingefügt ist, beträgt FCP etwa 2,7 Sekunden. In der Version, in der alles inline ist, beträgt die FCP etwa 5,8 Sekunden.
Gehen Sie mit Inline-Elementen in HTML, insbesondere base64-codierten Ressourcen sorgsam vor. 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. Entwickler verlassen sich nicht nur auf Interaktivität, sondern auch, um Inhalte selbst bereitzustellen. Dies führt in gewisser Weise zu einer besseren Erfahrung für Entwickler: Vorteile für Entwickler bedeuten jedoch nicht immer auch Vorteile für die Nutzer.
Ein Muster, das den Preloader-Scanner austricksen kann, ist das Rendern von Markup mit clientseitigem JavaScript:
Wenn Markup-Nutzlasten in JavaScript im Browser enthalten sind und vollständig von dort gerendert werden, sind alle Ressourcen in dieser Auszeichnung für den Preload-Scanner 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 Serverdarstellung, 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 Pre-Load-Scanners hinaus. Wenn Sie beispielsweise JavaScript einführen, um eine Erfahrung zu ermöglichen, die nicht erforderlich ist, führt dies zu unnötiger Verarbeitungszeit, die sich auf die Interaction to Next Paint (INP) auswirken kann. Das Rendern extrem großer Markups auf dem Client führt mit größerer Wahrscheinlichkeit zu langen Aufgaben als bei der gleichen Menge an Markup, die 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. Vom Client gerendertes Markup hingegen wird als eine einzelne, monolithische Aufgabe gehandhabt, die sich auf den INP einer Seite auswirken 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 werden kann und nicht vom Client gerendert wird? Lautet die Antwort „nein“, sollte nach Möglichkeit serverseitiges Rendering (SSR) oder statisch generiertes Markup in Betracht gezogen werden, da der Scanner so wichtige Ressourcen vorab ermitteln und bei Bedarf abrufen kann.
Falls für Ihre Seite JavaScript erforderlich ist, um einige Teile Ihres Seiten-Markups mit Funktionen zu versehen, können Sie dies mit SSR auch mithilfe von einfachem JavaScript oder Hydration tun, um das Beste aus beiden Welten herauszuholen.
Der Preload-Scanner hilft Ihnen
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, die bei vielen Messwerten, einschließlich einiger Web Vitals, bessere Ergebnisse liefert.
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 sucht, wenn er blockiert wird, um opportunistisch Ressourcen zu finden, die er früher abrufen kann.
- Ressourcen, die nicht in der vom Server bei der ersten Navigationsanfrage bereitgestellten Markup vorhanden sind, können vom Preload-Scanner nicht gefunden werden. Der Scanner zum Vorabladen kann unter anderem folgende Ursachen haben:
- Einfügen von Ressourcen in das DOM mit JavaScript, z. B. Skripts, Bilder, Stylesheets oder andere Elemente, die in der anfänglichen Markup-Nutzlast vom Server besser geeignet wären.
- Lazy Loading von „above the fold“-Bildern oder iFrames mithilfe einer JavaScript-Lösung
- Markup wird auf dem Client gerendert und kann Verweise auf untergeordnete Dokumentressourcen mit JavaScript enthalten.
- 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 ein Muster nicht vermeiden können, das sich negativ auf die Geschwindigkeit des Vorabladenscanners auswirkt, sollten Sie den Ressourcenhinweis rel=preload
berücksichtigen. Falls Sie rel=preload
verwenden, testen Sie mit Lab-Tools, ob Sie den gewünschten Effekt erzielen. Schließlich sollten Sie nicht zu viele Ressourcen vorab laden, denn wenn Sie alles priorisieren, wird es nichts mehr geben.
Ressourcen
- Asynchrone Skripts mit Inline-Skripts als schädlich eingestuft werden
- So werden Seiten mit dem Preloader des Browsers schneller geladen
- Wichtige Assets vorab laden, um die Ladegeschwindigkeit zu verbessern
- Frühzeitige Netzwerkverbindungen herstellen, um die wahrgenommene Seitengeschwindigkeit zu verbessern
- Largest Contentful Paint optimieren
Hero-Image von Unsplash, von Mohammad Rahmani