Bewertung der Ladeleistung im Feld mit Navigation Timing und Resource Timing

Machen Sie sich mit den Grundlagen der Verwendung der Navigation und Resource Timing APIs vertraut, um die Ladeleistung vor Ort zu bewerten.

Wenn Sie die Verbindungsdrosselung im Netzwerkbereich in den Entwicklertools eines Browsers (oder in Lighthouse in Chrome) zur Bewertung der Ladeleistung verwendet haben, 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 zu Labdaten und nicht zu Felddaten führen.

Synthetische Tests sind nicht grundsätzlich schlecht. Sie sind jedoch nicht repräsentativ dafür, wie schnell Ihre Website für echte Nutzer geladen wird. Dafür sind Felddaten erforderlich, die Sie von den Navigation Timing und Resource Timing APIs erfassen können.

APIs für die Bewertung der Ladeleistung vor Ort

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

  • Navigation Timing misst die Geschwindigkeit von Anfragen für HTML-Dokumente, d. h. Navigationsanfragen.
  • Ressourcentiming misst die Geschwindigkeit von Anfragen für dokumentenabhängige Ressourcen wie CSS, JavaScript, Bilder usw.

Die Daten dieser APIs werden in einem Zwischenspeicher für Leistungseinträge verfügbar gemacht, auf den im Browser mit JavaScript zugegriffen werden kann. Es gibt mehrere Möglichkeiten, einen Leistungspuffer abzufragen, häufig wird jedoch performance.getEntriesByType verwendet:

// 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 Zwischenspeicher für Leistungseinträge abrufen möchten. 'navigation' und 'resource' rufen Zeiten für die Navigation Timing API bzw. die Resource Timing API ab.

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

Lebensdauer und Zeitpunkt einer Netzwerkanfrage

Das Erfassen und Analysieren von Navigations- und Ressourcenzeiten ist eine Art Archäologie, da Sie das flüchtige Leben einer Netzwerkanfrage nachträglich rekonstruieren. Manchmal hilft es, Konzepte zu visualisieren und bei Netzwerkanfragen die Entwicklertools des Browsers zu verwenden.

Diagramm mit Netzwerktimings, wie in den Entwicklertools von Chrome dargestellt. Die Zeitangaben beziehen sich auf die Anfragewarteschlange, die Verbindungsverhandlung, die Anfrage selbst und die Antwort in farbcodierten Balken.
Visualisierung einer Netzwerkanfrage im Bereich „Netzwerk“ der Chrome-Entwicklertools

Der Lebenszyklus einer Netzwerkanfrage umfasst verschiedene Phasen, z. B. DNS-Lookup, Verbindungsaufbau, TLS-Verhandlung usw. Diese Zeitangaben werden als DOMHighResTimestamp dargestellt. Je nach Browser kann der Detaillierungsgrad der Zeitangaben auf Mikrosekunden beschränkt oder auf Millisekunden aufgerundet werden. Sehen wir uns diese Phasen genauer an 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 die Domain in eine IP-Adresse umzuwandeln. Dieser Vorgang kann viel Zeit in Anspruch nehmen. Sie sollten auch Zeit in der Praxis messen. Navigation Timing und Resource Timing stellen zwei DNS-bezogene Timings bereit:

  • Bei domainLookupStart beginnt der DNS-Lookup.
  • Bei domainLookupEnd endet der DNS-Lookup.

Sie können die gesamte DNS-Lookup-Zeit berechnen, indem Sie den Startmesswert vom Endmesswert subtrahieren:

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

Verbindungsverhandlung

Ein weiterer Faktor, der die Ladeleistung beeinflusst, ist die Verbindungsaushandlung. Hierbei handelt es sich um Latenzen beim Herstellen einer Verbindung zu einem Webserver. Wenn HTTPS verwendet wird, beinhaltet dieser Prozess auch die TLS-Verhandlungszeit. Die Verbindungsphase besteht aus drei Zeitpunkten:

  • Bei connectStart beginnt der Browser, eine Verbindung zu einem Webserver herzustellen.
  • secureConnectionStart gibt an, wann der Kunde die TLS-Verhandlung beginnt.
  • connectEnd ist der Zeitpunkt, an dem die Verbindung zum Webserver hergestellt wurde.

Das Messen der Gesamtverbindungszeit ähnelt der Messung der gesamten DNS-Lookup-Zeit: Sie subtrahieren das Start- und das End-Timing. Es gibt jedoch noch eine zusätzliche secureConnectionStart-Property, die möglicherweise 0 ist, wenn HTTPS nicht verwendet wird oder wenn die Verbindung dauerhaft ist. Wenn Sie die Verhandlungszeit für TLS messen möchten, müssen Sie Folgendes 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 die Verbindungsverhandlung beendet sind, kommen die Zeiten im Zusammenhang mit dem Abrufen von Dokumenten und ihren abhängigen Ressourcen ins Spiel.

Anfragen und Antworten

Die Ladeleistung wird von zwei Faktoren beeinflusst:

  • Extrinsische Faktoren:z. B. Latenz und Bandbreite. Über die Auswahl eines Hosting-Unternehmens und eines CDN hinaus liegt die Entscheidung größtenteils nicht in unserer Kontrolle, da die Nutzer von überall aus auf das Internet zugreifen können.
  • Intrinsische Faktoren:Dazu gehören z. B. server- und clientseitige Architekturen sowie die Ressourcengröße und unsere Optimierungsfähigkeit für diese Dinge, auf die wir Einfluss haben.

Beide Faktoren beeinflussen die Ladeleistung. Das mit diesen Faktoren verbundene Timing ist von entscheidender Bedeutung, da sie beschreiben, wie lange der Download von Ressourcen dauert. Sowohl „Navigation Timing“ als auch „Resource Timing“ beschreiben die Ladeleistung mit den folgenden Messwerten:

  • fetchStart gibt an, wann der Browser mit dem Abrufen einer Ressource (Ressourcentiming) oder eines Dokuments für eine Navigationsanfrage (Navigationstiming) beginnt. Dies geht der eigentlichen Anfrage voraus und ist der Punkt, an dem der Browser den Cache prüft (z. B. HTTP- und Cache-Instanzen).
  • workerStart gibt an, wann die Verarbeitung einer Anfrage im Event-Handler fetch eines Service Workers beginnt. Der Wert ist 0, wenn die aktuelle Seite von keinem Service Worker gesteuert wird.
  • Bei requestStart stellt der Browser die Anfrage.
  • Bei responseStart kommt das erste Byte der Antwort an.
  • Bei responseEnd kommt das letzte Byte der Antwort an.

Mit diesen Zeitangaben können Sie mehrere Aspekte der Ladeleistung messen, z. B. die Cache-Suche innerhalb eines Service Workers 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-/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 mögliche Messungen

Navigation Timing und Resource Timing sind für mehr als das, was in den Beispielen oben beschrieben nützlich ist, nützlich. Hier sind einige andere Situationen mit relevanten Zeiten, die es sich lohnen könnten, sie sich genauer anzusehen:

  • Seitenweiterleitungen:Weiterleitungen sind eine übersehene Ursache zusätzlicher Latenz, insbesondere bei Weiterleitungsketten. Die Latenz wird auf verschiedene Weise hinzugefügt, z. B. durch HTTP-zu-HTTP-Hops sowie 302/nicht zwischengespeicherte 301-Weiterleitungen. Die redirectStart-, redirectEnd- und redirectCount-Timings sind hilfreich, um die Weiterleitungslatenz zu beurteilen.
  • Entladen von Dokumenten:Auf Seiten, auf denen Code in einem unload-Event-Handler ausgeführt wird, muss der Browser diesen Code ausführen, bevor er die nächste Seite aufrufen kann. unloadEventStart und unloadEventEnd messen das Entladen von Dokumenten.
  • Dokumentverarbeitung: Die Verarbeitungszeit von Dokumenten kann nur dann sehr lang sein, wenn Ihre Website sehr große HTML-Nutzlasten sendet. Wenn dies auf deine Situation zutrifft, könnten die Zeitangaben für domInteractive, domContentLoadedEventStart, domContentLoadedEventEnd und domComplete von Interesse sein.

Timings im Anwendungscode erhalten

Für alle bisher gezeigten Beispiele wird performance.getEntriesByType verwendet. Es gibt jedoch auch andere Möglichkeiten, den Zwischenspeicher für Leistungseinträge abzufragen, z. B. performance.getEntriesByName und performance.getEntries. Diese Methoden sind in Ordnung, wenn nur die Lichtanalyse erforderlich ist. In anderen Situationen können sie jedoch zu einer übermäßigen Hauptthread-Arbeit 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.

Zum Erfassen von Einträgen aus dem Zwischenspeicher für Leistungseinträge wird empfohlen, einen PerformanceObserver zu verwenden. PerformanceObserver wartet auf Leistungseinträge und stellt sie bereit, wenn sie dem Zwischenspeicher 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 Zwischenspeicher für Leistungseinträge etwas unangenehm wirken. Es ist jedoch besser, den Hauptthread mit Aufgaben zu verknüpfen, die keinen kritischen und nutzerorientierten Zweck erfüllen.

Zuhause anrufen

Nachdem Sie alle benötigten Zeitangaben erfasst haben, können Sie sie zur weiteren Analyse an einen Endpunkt senden. Dafür gibt es zwei Möglichkeiten: mit navigator.sendBeacon oder mit fetch, bei dem die Option keepalive festgelegt ist. Bei beiden Methoden wird eine Anfrage nicht blockierend an einen angegebenen Endpunkt gesendet. Die Anfrage wird so in die Warteschlange gestellt, dass die aktuelle Seitensitzung bei Bedarf überschritten wird:

// 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()));

// The endpoint to transmit the encoded data to
const endpoint = '/analytics';

// Check for fetch keepalive support
if ('keepalive' in Request.prototype) {
  fetch(endpoint, {
    method: 'POST',
    body: data,
    keepalive: true,
    headers: {
      'Content-Type': 'application/json'
    }
  });
} else if ('sendBeacon' in navigator) {
  // Use sendBeacon as a fallback
  navigator.sendBeacon(endpoint, data);
}

In diesem Beispiel kommt der JSON-String in einer POST-Nutzlast an, die Sie nach Bedarf in einem Anwendungs-Back-End decodieren und verarbeiten/speichern können.

Zusammenfassung

Nachdem Sie Metriken erfasst haben, müssen Sie herausfinden, wie Sie diese Felddaten analysieren. Bei der Analyse von Felddaten sollten Sie einige allgemeine Regeln beachten, um aussagekräftige Schlüsse zu ziehen:

  • Vermeiden Sie Durchschnittswerte, da diese nicht repräsentativ für die Nutzererfahrung sind und durch Ausreißer verfälscht werden können.
  • Abhängig von Perzentilen. In Datasets mit zeitbasierten Leistungsmesswerten gilt: Je niedriger der Wert, desto besser. Wenn Sie also niedrige Perzentile priorisieren, achten Sie nur auf die schnellsten Websitevarianten.
  • Priorisieren Sie die Long Tail von Werten. Wenn Sie Websitevarianten ab dem 75. Perzentil priorisieren, fokussieren Sie sich auf die langsamsten Websites.

Dieser Leitfaden stellt keine umfassende Ressource zur Navigation oder zum Ressourcentiming dar, sondern stellt einen Ausgangspunkt dar. Nachfolgend finden Sie einige zusätzliche Ressourcen, die hilfreich sein könnten:

Mit diesen APIs und den von ihnen bereitgestellten Daten sind Sie besser gerüstet, um zu verstehen, wie die Ladeleistung von echten Nutzern wahrgenommen wird. Dies gibt Ihnen mehr Vertrauen bei der Diagnose und Behebung von Problemen mit der Ladeleistung vor Ort.