Veröffentlicht: 31. März 2014
Um kritische Leistungsengpässe im Renderingpfad zu identifizieren und zu beheben, müssen Sie die häufigsten Fallstricke kennen. Eine Demo zur Identifizierung häufiger Leistungsmuster hilft Ihnen, Ihre Seiten zu optimieren.
Durch die Optimierung des kritischen Rendering-Pfads kann der Browser die Seite so schnell wie möglich anzeigen. Schnellere Seiten führen zu mehr Interaktionen, mehr aufgerufenen Seiten und besseren Conversions. Um die Zeit zu minimieren, die ein Besucher vor einem leeren Bildschirm verbringt, müssen wir optimieren, welche Ressourcen geladen werden und in welcher Reihenfolge.
Um diesen Prozess zu veranschaulichen, beginnen wir mit dem einfachsten Fall und bauen die Seite schrittweise mit zusätzlichen Ressourcen, Stilen und Anwendungslogik auf. Dabei optimieren wir jeden Fall und sehen uns auch an, wo es zu Problemen kommen kann.
Bisher haben wir uns ausschließlich darauf konzentriert, was im Browser passiert, nachdem die Ressource (CSS-, JS- oder HTML-Datei) zur Verarbeitung verfügbar ist. Die Zeit, die zum Abrufen der Ressource aus dem Cache oder aus dem Netzwerk benötigt wird, wurde ignoriert. Wir nehmen an, dass Folgendes zutrifft:
- Ein Netzwerk-Roundtrip (Propagationslatenz) zum Server dauert 100 ms.
- Die Serverantwortzeit beträgt 100 ms für das HTML-Dokument und 10 ms für alle anderen Dateien.
Hello World-Erlebnis
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Critical Path: No Style</title>
</head>
<body>
<p>Hello <span>web performance</span> students!</p>
<div><img src="awesome-photo.jpg" /></div>
</body>
</html>
Beginnen Sie mit einfachem HTML-Markup und einem einzelnen Bild. Verwenden Sie kein CSS oder JavaScript. Öffnen Sie dann den Bereich „Netzwerk“ in den Chrome-Entwicklertools und sehen Sie sich die resultierende Ressourcenabfolge an:
Wie erwartet dauerte der Download der HTML-Datei etwa 200 ms. Der transparente Teil der blauen Linie gibt an, wie lange der Browser im Netzwerk wartet, ohne Antwortbytes zu erhalten. Der durchgezogene Teil zeigt die Zeit an, die für den Abschluss des Downloads nach Empfang der ersten Antwortbytes benötigt wird. Der HTML-Download ist winzig (< 4 KB), sodass wir nur einen einzigen Roundtrip benötigen, um die vollständige Datei abzurufen. Das Abrufen des HTML-Dokuments dauert daher etwa 200 ms, wobei die Hälfte der Zeit auf das Netzwerk und die andere Hälfte auf die Serverantwort wartet.
Sobald die HTML-Inhalte verfügbar sind, parset der Browser die Bytes, wandelt sie in Tokens um und erstellt den DOM-Baum. In den DevTools wird unten die Zeit für das DOMContentLoaded-Ereignis (216 ms) angezeigt, was auch der blauen vertikalen Linie entspricht. Die Lücke zwischen dem Ende des HTML-Downloads und der blauen vertikalen Linie (DOMContentLoaded) ist die Zeit, die der Browser benötigt, um den DOM-Baum zu erstellen. In diesem Fall sind es nur wenige Millisekunden.
Das Ereignis domContentLoaded
wurde durch unser „tolles Foto“ nicht blockiert. Wir können den Renderbaum erstellen und die Seite sogar darstellen, ohne auf jedes Asset auf der Seite zu warten: Nicht alle Ressourcen sind für eine schnelle First Paint erforderlich. Wenn wir vom kritischen Rendering-Pfad sprechen, meinen wir in der Regel das HTML-Markup, CSS und JavaScript. Bilder blockieren das erste Rendern der Seite nicht. Wir sollten jedoch versuchen, die Bilder so schnell wie möglich zu zeichnen.
Das Ereignis load
(auch onload
genannt) ist jedoch im Bild blockiert: In den DevTools wird das Ereignis onload
mit 335 ms angegeben. Das Ereignis onload
markiert den Punkt, an dem alle für die Seite erforderlichen Ressourcen heruntergeladen und verarbeitet wurden. An diesem Punkt kann das Ladesymbol im Browser (die rote vertikale Linie in der Abfolge) verschwinden.
JavaScript und CSS hinzufügen
Unsere „Hello World“-Seite mag einfach erscheinen, aber im Hintergrund passiert eine Menge. In der Praxis benötigen wir aber nicht nur HTML: Es ist wahrscheinlich, dass wir ein CSS-Stylesheet und ein oder mehrere Scripts haben, um unserer Seite Interaktivität zu verleihen. Probieren Sie beides aus, um zu sehen, was passiert:
<!DOCTYPE html>
<html>
<head>
<title>Critical Path: Measure Script</title>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link href="style.css" rel="stylesheet" />
</head>
<body onload="measureCRP()">
<p>Hello <span>web performance</span> students!</p>
<div><img src="awesome-photo.jpg" /></div>
<script src="timing.js"></script>
</body>
</html>
Vor dem Hinzufügen von JavaScript und CSS:
Mit JavaScript und CSS:
Wenn Sie externe CSS- und JavaScript-Dateien hinzufügen, werden der Vermittlungsabfolge zwei zusätzliche Anfragen hinzugefügt, die alle ungefähr gleichzeitig vom Browser gesendet werden. Beachten Sie jedoch, dass der Zeitunterschied zwischen den Ereignissen domContentLoaded
und onload
jetzt viel geringer ist.
Was ist passiert?
- Im Gegensatz zu unserem einfachen HTML-Beispiel müssen wir auch die CSS-Datei abrufen und parsen, um das CSSOM zu erstellen. Außerdem benötigen wir sowohl das DOM als auch das CSSOM, um den Renderbaum zu erstellen.
- Da die Seite auch eine JavaScript-Datei enthält, die den Parser blockiert, wird das
domContentLoaded
-Ereignis blockiert, bis die CSS-Datei heruntergeladen und geparst wurde. Da das JavaScript das CSSOM abfragen könnte, müssen wir die CSS-Datei blockieren, bis sie heruntergeladen wurde, bevor wir JavaScript ausführen können.
Was passiert, wenn wir unser externes Script durch ein Inline-Script ersetzen? Selbst wenn das Script direkt in die Seite eingebettet ist, kann es vom Browser erst ausgeführt werden, wenn das CSSOM erstellt wurde. Kurz gesagt: Eingebettetes JavaScript wird ebenfalls vom Parser blockiert.
Wird die Seite durch das Einfügen des Scripts trotz Blockierung durch CSS schneller gerendert? Probieren Sie es aus und sehen Sie, was passiert.
Externes JavaScript:
Inline-JavaScript:
Wir stellen eine Anfrage weniger, aber sowohl die onload
- als auch die domContentLoaded
-Zeiten sind praktisch gleich. Warum? Nun, es spielt keine Rolle, ob das JavaScript inline oder extern ist, denn sobald der Browser das Script-Tag findet, wird es blockiert und wartet, bis das CSSOM erstellt wurde. Außerdem lädt der Browser in unserem ersten Beispiel sowohl CSS als auch JavaScript parallel herunter und der Download ist ungefähr gleichzeitig abgeschlossen. In diesem Fall bringt das Einfügen des JavaScript-Codes nicht viel. Es gibt jedoch mehrere Strategien, mit denen sich das Rendern der Seite beschleunigen lässt.
Wie Sie wissen, werden alle Inline-Scripts vom Parser blockiert. Externen Scripts können wir jedoch das async
-Attribut hinzufügen, um den Parser zu entsperren. Heben Sie das Inline-Einfügen auf und versuchen Sie es noch einmal:
<!DOCTYPE html>
<html>
<head>
<title>Critical Path: Measure Async</title>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link href="style.css" rel="stylesheet" />
</head>
<body onload="measureCRP()">
<p>Hello <span>web performance</span> students!</p>
<div><img src="awesome-photo.jpg" /></div>
<script async src="timing.js"></script>
</body>
</html>
Parser-blockierendes (externes) JavaScript:
Asynchrones (externes) JavaScript:
Viel besser! Das Ereignis domContentLoaded
wird kurz nach dem Parsen der HTML-Datei ausgelöst. Der Browser weiß, dass JavaScript nicht blockiert werden darf. Da es keine anderen Scripts gibt, die den Parser blockieren, kann die CSSOM-Konstruktion auch parallel erfolgen.
Alternativ hätten wir sowohl das CSS als auch das JavaScript einfügen können:
<!DOCTYPE html>
<html>
<head>
<title>Critical Path: Measure Inlined</title>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<style>
p {
font-weight: bold;
}
span {
color: red;
}
p span {
display: none;
}
img {
float: right;
}
</style>
</head>
<body>
<p>Hello <span>web performance</span> students!</p>
<div><img src="awesome-photo.jpg" /></div>
<script>
var span = document.getElementsByTagName('span')[0];
span.textContent = 'interactive'; // change DOM text content
span.style.display = 'inline'; // change CSSOM property
// create a new element, style it, and append it to the DOM
var loadTime = document.createElement('div');
loadTime.textContent = 'You loaded this page on: ' + new Date();
loadTime.style.color = 'blue';
document.body.appendChild(loadTime);
</script>
</body>
</html>
Beachten Sie, dass die domContentLoaded
-Zeit im Grunde dieselbe ist wie im vorherigen Beispiel. Anstatt unser JavaScript als asynchron zu kennzeichnen, haben wir sowohl das CSS als auch das JS in die Seite selbst eingebettet. Dadurch wird unsere HTML-Seite viel größer, aber der Vorteil ist, dass der Browser nicht warten muss, um externe Ressourcen abzurufen. Alles ist direkt auf der Seite.
Wie Sie sehen, ist die Optimierung des kritischen Rendering-Pfads selbst bei einer sehr einfachen Seite keine triviale Übung: Wir müssen die Abhängigkeitsgrafik zwischen verschiedenen Ressourcen verstehen, die kritischen Ressourcen identifizieren und zwischen verschiedenen Strategien für die Einbindung dieser Ressourcen auf der Seite wählen. Es gibt keine allgemeingültige Lösung für dieses Problem, da jede Seite anders ist. Sie müssen selbst einen ähnlichen Prozess durchlaufen, um die optimale Strategie zu finden.
Sehen wir uns an, ob wir einige allgemeine Leistungsmuster identifizieren können.
Leistungsmuster
Die einfachste Seite besteht nur aus dem HTML-Markup, ohne CSS, JavaScript oder andere Arten von Ressourcen. Um diese Seite zu rendern, muss der Browser die Anfrage initiieren, auf das Eintreffen des HTML-Dokuments warten, es parsen, das DOM erstellen und es dann auf dem Bildschirm rendern:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Critical Path: No Style</title>
</head>
<body>
<p>Hello <span>web performance</span> students!</p>
<div><img src="awesome-photo.jpg" /></div>
</body>
</html>
Die Zeit zwischen T0 und T1 erfasst die Netzwerk- und Serververarbeitungszeiten. Im Idealfall (wenn die HTML-Datei klein ist) wird das gesamte Dokument mit nur einem Netzwerk-Roundtrip abgerufen. Aufgrund der Funktionsweise der TCP-Transportprotokolle sind für größere Dateien möglicherweise mehr Umläufe erforderlich. Im besten Fall hat die obige Seite also einen kritischen Renderingpfad mit nur einer Umdrehung.
Sehen wir uns nun dieselbe Seite mit einer externen CSS-Datei an:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link href="style.css" rel="stylesheet" />
</head>
<body>
<p>Hello <span>web performance</span> students!</p>
<div><img src="awesome-photo.jpg" /></div>
</body>
</html>
Noch einmal müssen wir einen Netzwerk-Roundtrip ausführen, um das HTML-Dokument abzurufen. Das abgerufene Markup gibt uns dann an, dass wir auch die CSS-Datei benötigen. Das bedeutet, dass der Browser zum Server zurückkehren und das CSS abrufen muss, bevor er die Seite auf dem Bildschirm rendern kann. Daher sind für diese Seite mindestens zwei Hin- und Rückwege erforderlich, bevor sie angezeigt werden kann. Noch einmal: Die CSS-Datei kann mehrere Rundreisen erfordern, daher der Hinweis auf „minimal“.
Im Folgenden finden Sie einige Begriffe, die wir für den kritischen Rendering-Pfad verwenden:
- Kritische Ressource:Ressource, die das erste Rendering der Seite blockieren kann.
- Länge des kritischen Pfads: Anzahl der Roundtrips oder die Gesamtzeit, die zum Abrufen aller kritischen Ressourcen erforderlich ist.
- Kritische Byte:Die Gesamtzahl der Byte, die für das erste Rendern der Seite erforderlich sind. Dies ist die Summe der Übertragungsdateigrößen aller kritischen Ressourcen. Unser erstes Beispiel mit einer einzelnen HTML-Seite enthielt eine einzige kritische Ressource (das HTML-Dokument). Die Länge des kritischen Pfads entsprach ebenfalls einem Netzwerk-Roundtrip (vorausgesetzt, die Datei war klein) und die Gesamtzahl der kritischen Bytes entsprach nur der Übertragungsgröße des HTML-Dokuments selbst.
Vergleichen Sie dies nun mit den Eigenschaften des kritischen Pfads aus dem vorherigen HTML- und CSS-Beispiel:
- 2 kritische Ressourcen
- 2 oder mehr Umläufe für die minimale kritische Pfadlänge
- 9 KB kritische Bytes
Wir benötigen sowohl HTML als auch CSS, um den Renderbaum zu erstellen. Daher sind sowohl HTML als auch CSS kritische Ressourcen: Das CSS wird erst abgerufen, nachdem der Browser das HTML-Dokument erhalten hat. Daher beträgt die Länge des kritischen Pfads mindestens zwei Rundreisen. Beide Ressourcen zusammen ergeben insgesamt 9 KB kritische Byte.
Fügen Sie jetzt eine zusätzliche JavaScript-Datei hinzu.
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link href="style.css" rel="stylesheet" />
</head>
<body>
<p>Hello <span>web performance</span> students!</p>
<div><img src="awesome-photo.jpg" /></div>
<script src="app.js"></script>
</body>
</html>
Wir haben app.js
hinzugefügt, das sowohl ein externes JavaScript-Asset auf der Seite als auch eine Parser-Blockierungsressource (d. h. eine kritische Ressource) ist. Schlimmer noch: Um die JavaScript-Datei auszuführen, müssen wir das CSSOM blockieren und darauf warten. Wie Sie sich erinnern, kann JavaScript das CSSOM abfragen. Daher wird der Browser angehalten, bis style.css
heruntergeladen und das CSSOM erstellt wurde.
Wenn wir uns jedoch die „Netzwerkabfolge“ dieser Seite ansehen, sehen wir, dass sowohl die CSS- als auch die JavaScript-Anfragen ungefähr gleichzeitig gestartet werden. Der Browser ruft die HTML-Datei ab, erkennt beide Ressourcen und startet beide Anfragen. Daher hat die im vorherigen Bild gezeigte Seite die folgenden Merkmale des kritischen Pfads:
- 3 kritische Ressourcen
- 2 oder mehr Umläufe für die minimale kritische Pfadlänge
- 11 KB kritische Bytes
Wir haben jetzt drei kritische Ressourcen mit insgesamt 11 KB kritischen Bytes, aber die Länge des kritischen Pfads beträgt weiterhin zwei Umläufe, da wir CSS und JavaScript parallel übertragen können. Wenn Sie die Eigenschaften Ihres kritischen Rendering-Pfads ermitteln möchten, müssen Sie die kritischen Ressourcen identifizieren und wissen, wie der Browser die Abrufe plant.
Nach einem Gespräch mit unseren Websiteentwicklern haben wir festgestellt, dass das JavaScript, das wir auf unserer Seite eingebunden haben, nicht blockiert werden muss. Es gibt einige Analyse- und anderen Code, der das Rendern unserer Seite nicht blockieren muss. Mit diesen Informationen können wir dem <script>
-Element das async
-Attribut hinzufügen, um den Parser zu entsperren:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link href="style.css" rel="stylesheet" />
</head>
<body>
<p>Hello <span>web performance</span> students!</p>
<div><img src="awesome-photo.jpg" /></div>
<script src="app.js" async></script>
</body>
</html>
Ein asynchrones Script bietet mehrere Vorteile:
- Das Script blockiert den Parser nicht mehr und ist nicht Teil des kritischen Rendering-Pfads.
- Da es keine anderen kritischen Scripts gibt, muss das CSS das Ereignis
domContentLoaded
nicht blockieren. - Je früher das Ereignis
domContentLoaded
ausgelöst wird, desto früher kann die Ausführung anderer Anwendungslogik beginnen.
Daher hat unsere optimierte Seite jetzt wieder zwei kritische Ressourcen (HTML und CSS) mit einer Mindestlänge des kritischen Pfades von zwei Rundreisen und insgesamt 9 KB kritischer Byte.
Wie würde das CSS-Stylesheet aussehen, wenn es nur für den Druck benötigt würde?
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link href="style.css" rel="stylesheet" media="print" />
</head>
<body>
<p>Hello <span>web performance</span> students!</p>
<div><img src="awesome-photo.jpg" /></div>
<script src="app.js" async></script>
</body>
</html>
Da die style.css-Ressource nur für den Druck verwendet wird, muss der Browser sie nicht blockieren, um die Seite zu rendern. Sobald der DOM-Aufbau abgeschlossen ist, hat der Browser genügend Informationen, um die Seite zu rendern. Daher hat diese Seite nur eine einzige kritische Ressource (das HTML-Dokument) und die minimale Länge des kritischen Rendering-Pfads ist ein Hin- und Rücklauf.