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

Grundlagen der Verwendung der Navigation Timing API und der Resource Timing API zur Bewertung der Ladeleistung in der Praxis

Veröffentlicht am 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. Sie können die Auswirkungen von Leistungsoptimierungen schnell mit einer konsistenten und stabilen Basisverbindungsgeschwindigkeit 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 mit der Navigation Timing API und der Resource Timing API erheben können.

APIs zur Bewertung der Ladeleistung in der Praxis

Die Navigation Timing API und die Resource Timing API sind zwei ähnliche APIs mit erheblichen Überschneidungen, die zwei unterschiedliche Dinge messen:

  • Navigation Timing misst die Geschwindigkeit von Anfragen nach HTML-Dokumenten (d. h. Navigationsanfragen).
  • Die Resource Timing API misst die Geschwindigkeit von Anfragen nach dokumentabhängigen Ressourcen wie CSS, JavaScript, Bildern und anderen Ressourcentypen.

Diese APIs stellen ihre Daten in einem Puffer für Leistungseinträge zur Verfügung, 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 den Typ der Einträge beschreibt, die Sie aus dem Puffer für Leistungseinträge abrufen möchten. 'navigation' und 'resource' rufen jeweils die Zeitmessungen für die Navigation Timing API und 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 in der Praxis, da Sie diese Zeitmessungen von Nutzern erheben können, wenn sie Ihre Website besuchen.

Lebenszyklus und Zeitmessungen einer Netzwerkanfrage

Das Erheben und Analysieren von Zeitmessungen für Navigation und Ressourcen ist wie Archäologie: Sie rekonstruieren das flüchtige Leben einer Netzwerkanfrage im Nachhinein. Manchmal hilft es, Konzepte zu visualisieren. Bei Netzwerkanfragen können Ihnen die Entwicklertools Ihres Browsers helfen.

Netzwerk-Timings, wie sie in den Entwicklertools von Chrome angezeigt werden. Die dargestellten Zeitangaben beziehen sich auf die Warteschlange für Anfragen, die Verbindungsverhandlung, die Anfrage selbst und die Antwort in farbcodierten Balken.
Visualisierung einer Netzwerkanfrage im Netzwerkbereich der Chrome-Entwicklertools

Der Lebenszyklus einer Netzwerkanfrage hat verschiedene Phasen, z. B. DNS-Lookup, Verbindungsaufbau, TLS-Aushandlung und andere Quellen für Latenz. Diese Zeitmessungen werden als ein DOMHighResTimestamp dargestellt. Je nach Browser kann die Granularität der Zeitmessungen bis auf die Mikrosekunde genau sein oder auf Millisekunden gerundet werden. Sie sollten diese Phasen im Detail untersuchen und sehen, wie sie mit der Navigation Timing API und der Resource Timing API zusammenhängen.

DNS-Lookup

Wenn ein Nutzer eine URL aufruft, wird das Domain Name System (DNS) abgefragt, um eine Domain in eine IP-Adresse zu übersetzen. Dieser Vorgang kann viel Zeit in Anspruch nehmen – Zeit, die Sie auch in der Praxis messen sollten. Die Navigation Timing API und die Resource Timing API stellen zwei DNS-bezogene Zeitmessungen zur Verfügung:

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

Die Gesamtzeit für den DNS-Lookup kann berechnet werden, indem der Startmesswert vom Endmesswert subtrahiert wird:

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

Verbindungsaufbau

Ein weiterer Faktor, der zur Ladeleistung beiträgt, ist der Verbindungsaufbau. Dabei entsteht Latenz beim Herstellen einer Verbindung zu einem Webserver. Wenn HTTPS verwendet wird, umfasst dieser Vorgang auch die Zeit für die TLS-Aushandlung. Die Verbindungsphase besteht aus drei Zeitmessungen:

  • connectStart ist der Zeitpunkt, an dem der Browser beginnt, eine Verbindung zu einem Webserver zu öffnen.
  • secureConnectionStart markiert den Zeitpunkt, an dem der Client mit der TLS-Aushandlung beginnt.
  • connectEnd ist der Zeitpunkt, an dem die Verbindung zum Webserver hergestellt wurde.

Die Gesamtverbindungszeit wird ähnlich wie die Gesamtzeit für den DNS-Lookup gemessen: Sie subtrahieren die Startzeitmessung von der Endzeitmessung. Es gibt jedoch eine zusätzliche secureConnectionStart-Property, die 0 sein kann, wenn HTTPS nicht verwendet wird oder wenn die Verbindung dauerhaft ist. Wenn Sie die Zeit für die TLS-Aushandlung messen möchten, müssen Sie das berücksichtigen:

// 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 der DNS-Lookup und der Verbindungsaufbau abgeschlossen sind, kommen Zeitmessungen für das Abrufen von Dokumenten und ihren abhängigen Ressourcen ins Spiel.

Anfragen und Antworten

Die Ladeleistung wird von zwei Arten von Faktoren beeinflusst:

  • Externe Faktoren:Dazu gehören Latenz und Bandbreite. Abgesehen von der Auswahl eines Hostingunternehmens und möglicherweise eines CDN haben wir (meistens) keinen Einfluss darauf, da Nutzer von überall aus auf das Web zugreifen können.
  • Interne Faktoren:Dazu gehören Server- und clientseitige Architekturen sowie die Ressourcengröße und unsere Fähigkeit, diese Dinge zu optimieren. Diese Faktoren können wir beeinflussen.

Beide Arten von Faktoren wirken sich auf die Ladeleistung aus. Zeitmessungen im Zusammenhang mit diesen Faktoren sind wichtig, da sie beschreiben, wie lange das Herunterladen von Ressourcen dauert. Sowohl die Navigation Timing API als auch die Resource Timing API beschreiben die Ladeleistung mit den folgenden Messwerten:

  • fetchStart markiert den Zeitpunkt, an dem der Browser beginnt, eine Ressource (Resource Timing) oder ein Dokument für eine Navigationsanfrage (Navigation Timing) abzurufen. Dies geschieht vor der eigentlichen Anfrage und ist der Zeitpunkt, an dem der Browser Caches prüft (z. B. HTTP- und Cache Instanzen).
  • workerStart markiert den Zeitpunkt, an dem eine Anfrage im fetch Ereignishandler eines Service Workers verarbeitet wird. Dieser Wert ist 0, wenn kein Service Worker die aktuelle Seite steuert.
  • requestStart ist der Zeitpunkt, an dem der Browser die Anfrage sendet.
  • responseStart ist der Zeitpunkt, an dem das erste Byte der Antwort eintrifft.
  • responseEnd ist der Zeitpunkt, an dem das letzte Byte der Antwort eintrifft.

Mit diesen Zeitmessungen können Sie mehrere Aspekte der Ladeleistung messen, z. B. den Cache-Lookup 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 Latenz von Anfragen und Antworten 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 Messungen

Die Navigation Timing API und die Resource Timing API sind für mehr als die in den vorherigen Beispielen beschriebenen Fälle nützlich. Hier sind einige weitere Situationen mit relevanten Zeitmessungen, die es sich anzusehen lohnt:

  • Seitenweiterleitungen:Weiterleitungen sind eine oft übersehene Quelle für zusätzliche Latenz, insbesondere Weiterleitungsketten. Latenz wird auf verschiedene Weise hinzugefügt, z. B. durch HTTP-zu-HTTPS-Hops sowie durch 302- und nicht gecachte 301-Weiterleitungen. Die Zeitmessungen redirectStart, redirectEnd und redirectCount sind hilfreich bei der Bewertung der Weiterleitungslatenz.
  • Entladen von Dokumenten: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. unloadEventStart und unloadEventEnd messen das Entladen von Dokumenten.
  • Dokumentverarbeitung:Die Zeit für die Dokumentverarbeitung ist möglicherweise nicht von Bedeutung, es sei denn, Ihre Website sendet sehr große HTML-Nutzlasten. Wenn dies auf Ihre Situation zutrifft, sind die Zeitmessungen domInteractive, domContentLoadedEventStart, domContentLoadedEventEnd und domComplete möglicherweise interessant.

Zeitmessungen im Code abrufen

In allen bisherigen Beispielen wird performance.getEntriesByType verwendet. Es gibt jedoch auch andere Möglichkeiten, den Puffer für Leistungseinträge abzufragen, z. B. performance.getEntriesByName und performance.getEntries. Diese Methoden sind in Ordnung, wenn nur eine einfache Analyse erforderlich ist. In anderen Situationen können sie jedoch zu einer übermäßigen Belastung des Hauptthreads führen, indem sie eine große Anzahl von Einträgen durchlaufen oder den Leistungspuffer wiederholt abfragen, um neue Einträge zu finden.

Die empfohlene Methode zum Erheben von Einträgen aus dem Puffer für Leistungseinträge ist die Verwendung eines PerformanceObserver. 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 Erheben von Zeitmessungen mag im Vergleich zum direkten Zugriff auf den Puffer für Leistungseinträge umständlich erscheinen, ist aber vorzuziehen, um den Hauptthread nicht mit Aufgaben zu belasten, die keinen kritischen und nutzerorientierten Zweck erfüllen.

Daten an einen Endpunkt senden

Sobald Sie alle erforderlichen Zeitmessungen erhoben haben, können Sie sie zur weiteren Analyse an einen Endpunkt senden. Dazu haben Sie zwei Möglichkeiten: entweder mit navigator.sendBeacon oder mit einer fetch-Anfrage, bei der die Option keepalive festgelegt ist. Bei beiden Methoden wird eine Anfrage nicht blockierend an einen bestimmten Endpunkt gesendet. Die Anfrage wird so in die Warteschlange gestellt, dass sie bei Bedarf die aktuelle Sitzung auf der Seite überdauert:

// 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 kommt der JSON-String in einer POST-Nutzlast an, die Sie nach Bedarf decodieren, verarbeiten und im Backend einer Anwendung speichern können.

Fazit

Sobald Sie Messwerte erhoben haben, müssen Sie entscheiden, wie Sie diese Felddaten analysieren. Bei der Analyse von Felddaten sind einige allgemeine Regeln zu beachten, damit Sie aussagekräftige Schlussfolgerungen ziehen können:

  • Vermeiden Sie Durchschnittswerte, da sie nicht repräsentativ für die Nutzererfahrung eines einzelnen Nutzers sind und durch Ausreißer verzerrt werden können.
  • Verlassen Sie sich auf Perzentile. Bei Datensätzen mit zeitbasierten Leistungsmesswerten gilt: Je niedriger, desto besser. Wenn Sie niedrige Perzentile priorisieren, berücksichtigen Sie also nur die schnellsten Nutzererfahrungen.
  • Priorisieren Sie den langen Schwanz der Werte. Wenn Sie Nutzererfahrungen ab dem 75. Perzentil oder höher priorisieren, konzentrieren Sie sich auf die langsamsten Nutzererfahrungen.

Diese Anleitung soll keine umfassende Ressource zur Navigation Timing API oder zur Resource Timing API sein, sondern nur ein Ausgangspunkt. Hier sind einige zusätzliche Ressourcen, die hilfreich sein können:

Mit diesen APIs und den von ihnen bereitgestellten Daten können Sie besser nachvollziehen, wie echte Nutzer die Ladeleistung erleben. So können Sie Probleme mit der Ladeleistung in der Praxis besser diagnostizieren und beheben.