So bewerten Sie die Ladeleistung vor Ort mithilfe von Navigation Timing und Resource Timing

Hier erfahren Sie die Grundlagen der Verwendung der Navigation and Resource Timing APIs zur Bewertung der Ladeleistung im Feld.

Veröffentlicht: 8. Oktober 2021

Wenn Sie die Verbindungsdrosselung im Netzwerkbereich der Entwicklertools eines Browsers (oder Lighthouse in Chrome) verwendet haben, um die Ladeleistung zu bewerten, wissen Sie, wie praktisch diese Tools für die Leistungsoptimierung sind. Mit einer konstanten und stabilen Verbindungsgeschwindigkeit können Sie die Auswirkungen von Leistungsoptimierungen schnell messen. Das einzige Problem ist, dass es sich um synthetische Tests handelt, die Labordaten und keine Felddaten liefern.

Synthetische Tests sind nicht grundsätzlich schlecht, aber sie sind nicht repräsentativ dafür, wie schnell Ihre Website für echte Nutzer geladen wird. Dazu sind Felddaten erforderlich, die Sie über die Navigation Timing API und die Resource Timing API erfassen können.

APIs zur Leistungsbewertung von Ladezeiten

Navigation Timing und Resource Timing sind zwei ähnliche APIs mit erheblicher Überschneidung, die zwei verschiedene Dinge messen:

  • Mit Navigation Timing wird die Geschwindigkeit von Anfragen für HTML-Dokumente (d. h. Navigationsanfragen) gemessen.
  • Mit Ressourcenzeit wird die Geschwindigkeit von Anfragen für dokumentabhängige Ressourcen wie CSS, JavaScript, Bilder und andere Ressourcentypen gemessen.

Diese APIs stellen ihre Daten in einem Leistungseintrags-Puffer bereit, auf den im Browser mit JavaScript zugegriffen werden kann. Es gibt mehrere Möglichkeiten, einen Leistungspuffer abzufragen. Eine gängige Methode ist die Verwendung von performance.getEntriesByType:

// Get Navigation Timing entries:
performance.getEntriesByType('navigation');

// Get Resource Timing entries:
performance.getEntriesByType('resource');

performance.getEntriesByType akzeptiert einen String, der die Art der Einträge beschreibt, die aus dem Puffer für Leistungseinträge abgerufen werden sollen. 'navigation' und 'resource' rufen Zeitangaben für die Navigation Timing API bzw. die Resource Timing API ab.

Die Menge an Informationen, die diese APIs liefern, kann überwältigend sein. Sie sind jedoch der Schlüssel zur Messung der Ladeleistung vor Ort, da Sie diese Zeitangaben von Nutzern erfassen können, wenn sie Ihre Website besuchen.

Ablauf und Zeitangaben einer Netzwerkanfrage

Das Erfassen und Analysieren von Navigations- und Ressourcenzeitpunkten ist in gewisser Weise mit Archäologie vergleichbar, da Sie das flüchtige Leben einer Netzwerkanfrage nachträglich rekonstruieren. Manchmal hilft es, Konzepte zu visualisieren. Bei Netzwerkanfragen können die Entwicklertools Ihres Browsers hilfreich sein.

Netzwerkzeitangaben aus den Chrome-Entwicklertools Die dargestellten Zeitangaben beziehen sich auf die Anfragewarteschlange, die Verbindungsverhandlung, die Anfrage selbst und die Antwort in farbcodierten Balken.
Visualisierung einer Netzwerkanfrage im Netzwerkbereich der Chrome-Entwicklertools

Das Leben einer Netzwerkanfrage umfasst verschiedene Phasen, z. B. DNS-Lookup, Verbindungsherstellung, TLS-Verhandlung und andere Latenzquellen. Diese Zeitangaben werden als DOMHighResTimestamp dargestellt. Je nach Browser werden die Zeitangaben möglicherweise auf Mikrosekunden genau oder auf Millisekunden gerundet. Sie sollten sich diese Phasen im Detail ansehen und wie sie mit dem Navigations-Timing und dem Ressourcen-Timing zusammenhängen.

DNS-Lookup

Wenn ein Nutzer eine URL aufruft, wird das Domain Name System (DNS) abgefragt, um eine Domain in eine IP-Adresse umzuwandeln. Dieser Vorgang kann viel Zeit in Anspruch nehmen – Zeit, die Sie sogar im Feld messen sollten. Unter „Navigation Timing“ (Navigationszeit) und „Resource Timing“ (Ressourcenzeit) werden zwei DNS-bezogene Zeitangaben angezeigt:

  • domainLookupStart ist der Beginn der DNS-Suche.
  • domainLookupEnd ist der Zeitpunkt, an dem der DNS-Lookup endet.

Die Gesamtdauer der DNS-Suche lässt sich berechnen, indem der Messwert „Start“ vom Messwert „Ende“ abgezogen wird:

// Measuring DNS lookup time
const [pageNav] = performance.getEntriesByType('navigation');
const totalLookupTime = pageNav.domainLookupEnd - pageNav.domainLookupStart;

Verbindungsverhandlung

Ein weiterer Faktor für die Ladeleistung ist die Verbindungsverhandlung, also die Latenz, die beim Herstellen einer Verbindung zu einem Webserver entsteht. Wenn HTTPS verwendet wird, umfasst dieser Prozess auch die TLS-Verhandlungszeit. Die Verbindungsphase besteht aus drei Zeiträumen:

  • connectStart ist der Zeitpunkt, zu dem der Browser eine Verbindung zu einem Webserver öffnet.
  • secureConnectionStart kennzeichnet den Beginn der TLS-Verhandlung durch den Client.
  • connectEnd bedeutet, dass die Verbindung zum Webserver hergestellt wurde.

Die Gesamtverbindungsdauer wird ähnlich wie die Gesamtdauer der DNS-Suche gemessen: Sie ziehen die Startzeit von der Endzeit ab. Es gibt jedoch eine zusätzliche secureConnectionStart-Eigenschaft, die 0 sein kann, wenn HTTPS nicht verwendet wird oder wenn die Verbindung persistent ist. Wenn Sie die TLS-Verhandlungszeit messen möchten, müssen Sie das beachten:

// Quantifying total connection time
const [pageNav] = performance.getEntriesByType('navigation');
const connectionTime = pageNav.connectEnd - pageNav.connectStart;
let tlsTime = 0; // <-- Assume 0 to start with

// Was there TLS negotiation?
if (pageNav.secureConnectionStart > 0) {
  // Awesome! Calculate it!
  tlsTime = pageNav.connectEnd - pageNav.secureConnectionStart;
}

Sobald die DNS-Suche und die Verbindungsverhandlung abgeschlossen sind, spielen die Zeitangaben für das Abrufen von Dokumenten und ihrer abhängigen Ressourcen eine Rolle.

Anfragen und Antworten

Die Ladeleistung wird von zwei Arten von Faktoren beeinflusst:

  • Extrinsische Faktoren:Dazu gehören beispielsweise Latenz und Bandbreite. Abgesehen von der Auswahl eines Hostinganbieters und möglicherweise eines CDN liegt sie (meistens) außerhalb unserer Kontrolle, da Nutzer von überall aus auf das Web zugreifen können.
  • Intrinsische Faktoren:Dazu gehören Aspekte wie server- und clientseitige Architekturen sowie die Ressourcengröße und unsere Fähigkeit, diese Aspekte zu optimieren. Diese Aspekte liegen in unserer Kontrolle.

Beide Arten von Faktoren wirken sich auf die Ladeleistung aus. Die Zeitangaben zu diesen Faktoren sind wichtig, da sie angeben, wie lange der Download von Ressourcen dauert. Sowohl „Navigations-Timing“ als auch „Ressourcen-Timing“ beschreiben die Ladeleistung mit den folgenden Messwerten:

  • fetchStart markiert, wann der Browser mit dem Abrufen einer Ressource (Ressourcen-Timing) oder eines Dokuments für eine Navigationsanfrage (Navigations-Timing) beginnt. Dieser Schritt erfolgt vor der eigentlichen Anfrage und ist der Zeitpunkt, an dem der Browser die Caches überprüft (z. B. HTTP- und Cache-Instanzen).
  • workerStart gibt an, wann eine Anfrage im fetch-Ereignishandler eines Service Workers verarbeitet wird. Dies ist 0, wenn die aktuelle Seite von keinem Service Worker gesteuert wird.
  • requestStart ist der Zeitpunkt, zu dem der Browser die Anfrage stellt.
  • responseStart ist der Zeitpunkt, zu dem das erste Byte der Antwort eintrifft.
  • responseEnd ist der Zeitpunkt, zu dem das letzte Byte der Antwort eintrifft.

Anhand dieser Zeitangaben können Sie mehrere Aspekte der Ladeleistung messen, z. B. die Cache-Suche in einem Service Worker und die Downloadzeit:

// Cache seek plus response time of the current document
const [pageNav] = performance.getEntriesByType('navigation');
const fetchTime = pageNav.responseEnd - pageNav.fetchStart;

// Service worker time plus response time
let workerTime = 0;

if (pageNav.workerStart > 0) {
  workerTime = pageNav.responseEnd - pageNav.workerStart;
}

Sie können auch andere Aspekte der Anfrage- und Antwortlatenz messen:

const [pageNav] = performance.getEntriesByType('navigation');

// Request time only (excluding redirects, DNS, and connection/TLS time)
const requestTime = pageNav.responseStart - pageNav.requestStart;

// Response time only (download)
const responseTime = pageNav.responseEnd - pageNav.responseStart;

// Request + response time
const requestResponseTime = pageNav.responseEnd - pageNav.requestStart;

Weitere Messwerte

Navigation Timing und Resource Timing sind nicht nur für die in den vorherigen Beispielen beschriebenen Zwecke nützlich. Hier sind einige weitere Situationen mit relevanten Zeiträumen, die Sie berücksichtigen sollten:

  • Seitenweiterleitungen:Weiterleitungen sind eine oft übersehene Quelle für zusätzliche Latenz, insbesondere Weiterleitungsketten. Die Latenz kann auf verschiedene Arten zustande kommen, z. B. durch HTTP-zu-HTTPs-Sprünge sowie 302-/nicht im Cache gespeicherte 301-Weiterleitungen. Die Zeitangaben für redirectStart, redirectEnd und redirectCount sind hilfreich, um die Weiterleitungslatenz zu beurteilen.
  • Entladen des Dokuments:Auf Seiten, auf denen Code in einem unload-Ereignishandler ausgeführt wird, muss der Browser diesen Code ausführen, bevor er zur nächsten Seite wechseln kann. Mit unloadEventStart und unloadEventEnd wird das Entladen von Dokumenten gemessen.
  • Dokumentenverarbeitung:Die Verarbeitungszeit von Dokumenten ist möglicherweise nicht von Bedeutung, es sei denn, Ihre Website sendet sehr große HTML-Nutzlast. Wenn das auf Sie zutrifft, sind die Zeitangaben für domInteractive, domContentLoadedEventStart, domContentLoadedEventEnd und domComplete möglicherweise interessant.

So rufen Sie Timings in Ihren Code auf

In allen bisher gezeigten Beispielen wird performance.getEntriesByType verwendet. Es gibt aber auch andere Möglichkeiten, den Leistungseintragsbuffer abzufragen, z. B. performance.getEntriesByName und performance.getEntries. Diese Methoden eignen sich gut, wenn nur eine einfache Analyse erforderlich ist. In anderen Situationen können sie jedoch zu übermäßiger Arbeit im Hauptthread führen, indem sie über eine große Anzahl von Einträgen iterieren oder sogar wiederholt den Leistungspuffer abfragen, um neue Einträge zu finden.

Wir empfehlen, zum Erfassen von Einträgen aus dem Leistungseintragspuffer eine PerformanceObserver zu verwenden. PerformanceObserver überwacht Leistungseinträge und stellt sie bereit, sobald sie dem Puffer hinzugefügt werden:

// Create the performance observer:
const perfObserver = new PerformanceObserver((observedEntries) => {
  // Get all resource entries collected so far:
  const entries = observedEntries.getEntries();

  // Iterate over entries:
  for (let i = 0; i < entries.length; i++) {
    // Do the work!
  }
});

// Run the observer for Navigation Timing entries:
perfObserver.observe({
  type: 'navigation',
  buffered: true
});

// Run the observer for Resource Timing entries:
perfObserver.observe({
  type: 'resource',
  buffered: true
});

Diese Methode zum Erfassen von Zeitangaben mag im Vergleich zum direkten Zugriff auf den Leistungseintragsbuffer etwas umständlich erscheinen, aber es ist besser, den Hauptthread nicht mit Aufgaben zu belegen, die keinen kritischen und nutzerorientierten Zweck erfüllen.

So rufen Sie zu Hause an

Sobald Sie alle benötigten Zeitangaben erfasst haben, können Sie sie zur weiteren Analyse an einen Endpunkt senden. Dazu gibt es zwei Möglichkeiten: entweder mit navigator.sendBeacon oder mit fetch und der Option keepalive. Bei beiden Methoden wird eine Anfrage auf nicht blockierende Weise an einen bestimmten Endpunkt gesendet. Die Anfrage wird so in die Warteschlange gestellt, dass sie bei Bedarf über die aktuelle Seitensitzung hinausdauert:

// Check for navigator.sendBeacon support:
if ('sendBeacon' in navigator) {
  // Caution: If you have lots of performance entries, don't
  // do this. This is an example for illustrative purposes.
  const data = JSON.stringify(performance.getEntries());

  // Send the data!
  navigator.sendBeacon('/analytics', data);
}

In diesem Beispiel wird der JSON-String in einer POST-Nutzlast empfangen, die Sie nach Bedarf decodieren, verarbeiten und in einem Anwendungs-Backend speichern können.

Fazit

Sobald Sie die Messwerte erfasst haben, müssen Sie herausfinden, wie Sie diese Felddaten analysieren. Bei der Analyse von Felddaten gibt es einige allgemeine Regeln, die Sie beachten sollten, damit Sie aussagekräftige Schlussfolgerungen ziehen können:

  • Vermeiden Sie Durchschnittswerte, da diese nicht repräsentativ für die Erfahrung eines Nutzers sind und durch Ausreißer verzerrt sein können.
  • Verlassen Sie sich auf Perzentile. Bei Datasets mit zeitbasierten Leistungsmesswerten gilt: Je niedriger der Wert, desto besser. Wenn Sie also niedrige Prozentile priorisieren, konzentrieren Sie sich nur auf die schnellsten Ergebnisse.
  • Priorisieren Sie die Longtail-Werte. Wenn Sie die Aufrufe mit einer Ladezeit von mindestens 75 % priorisieren, legen Sie den Fokus dorthin, wo er hingehört: auf die langsamsten Aufrufe.

Dieser Leitfaden ist nicht als umfassende Ressource zu Navigation oder Ressourcenzeit gedacht, sondern als Ausgangspunkt. Diese zusätzlichen Ressourcen könnten ebenfalls hilfreich sein:

Mit diesen APIs und den Daten, die sie liefern, können Sie besser nachvollziehen, wie die Ladeleistung von echten Nutzern wahrgenommen wird. Dies gibt Ihnen mehr Sicherheit bei der Diagnose und Behebung von Ladeleistungsproblemen vor Ort.