Évaluer les performances de chargement sur le terrain avec Navigation Timing et Resource Timing

Découvrez les bases de l'utilisation des API Navigation et Resource Timing pour évaluer les performances de chargement sur le terrain.

Publié le 8 octobre 2021

Si vous avez déjà utilisé le débit limité de la connexion dans le panneau "Réseau" des outils pour les développeurs d'un navigateur (ou Lighthouse dans Chrome) pour évaluer les performances de chargement, vous savez à quel point ces outils sont pratiques pour optimiser les performances. Vous pouvez mesurer rapidement l'impact des optimisations des performances avec une vitesse de connexion de référence cohérente et stable. Le seul problème est qu'il s'agit de tests synthétiques, qui fournissent des données expérimentales, et non des données réelles.

Les tests synthétiques ne sont pas nécessairement mauvais, mais ils ne reflètent pas la vitesse de chargement de votre site Web pour les utilisateurs réels. Cela nécessite des données de champ, que vous pouvez collecter à partir des API Navigation Timing et Resource Timing.

API vous permettant d'évaluer les performances de chargement sur le terrain

Navigation Timing et Resource Timing sont deux API similaires qui se chevauchent de manière significative et qui mesurent deux choses distinctes:

  • Navigation Timing mesure la vitesse des requêtes pour les documents HTML (c'est-à-dire les requêtes de navigation).
  • Resource Timing mesure la vitesse des requêtes pour les ressources dépendantes des documents, telles que les fichiers CSS, JavaScript, images et autres types de ressources.

Ces API exposent leurs données dans un tampon d'entrée de performances, auquel vous pouvez accéder dans le navigateur avec JavaScript. Il existe plusieurs façons d'interroger un tampon de performances, mais une méthode courante consiste à utiliser performance.getEntriesByType:

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

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

performance.getEntriesByType accepte une chaîne décrivant le type d'entrées que vous souhaitez récupérer à partir du tampon d'entrées de performances. 'navigation' et 'resource' récupèrent les temps de l'API Navigation Timing et de l'API Resource Timing, respectivement.

Ces API peuvent fournir beaucoup d'informations, mais elles sont essentielles pour mesurer les performances de chargement sur le terrain, car vous pouvez recueillir ces données auprès des utilisateurs lorsqu'ils visitent votre site Web.

Durée de vie et délais d'une requête réseau

La collecte et l'analyse des délais de navigation et de ressources ressemblent à de l'archéologie, car vous reconstruisez la vie éphémère d'une requête réseau après coup. Il est parfois utile de visualiser des concepts. Pour les requêtes réseau, les outils pour les développeurs de votre navigateur peuvent vous aider.

Temps de réseau, comme indiqué dans les outils de développement de Chrome. Les délais représentés correspondent à la mise en file d'attente des requêtes, à la négociation de la connexion, à la requête elle-même et à la réponse, dans des barres de couleurs.
Visualisation d'une requête réseau dans le panneau réseau des outils pour les développeurs Chrome

La durée de vie d'une requête réseau comporte des phases distinctes, telles que la résolution DNS, l'établissement de la connexion, la négociation TLS et d'autres sources de latence. Ces délais sont représentés par un DOMHighResTimestamp. Selon votre navigateur, la précision des temps peut être au microseconde près ou arrondie à la milliseconde. Vous devez examiner ces phases en détail et leur relation avec les paramètres Navigation Timing et Resource Timing.

résolution DNS

Lorsqu'un utilisateur accède à une URL, le système de noms de domaine (DNS) est interrogé pour traduire un domaine en adresse IP. Ce processus peut prendre un certain temps, que vous devrez même mesurer sur le terrain. Les paramètres Navigation Timing et Resource Timing exposent deux temps liés au DNS:

  • domainLookupStart correspond au début de la recherche DNS.
  • domainLookupEnd correspond à la fin de la résolution DNS.

Pour calculer la durée totale de la résolution DNS, soustrayez la métrique de début à la métrique de fin:

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

Négociation de connexion

La négociation de connexion, qui correspond à la latence induite lors de la connexion à un serveur Web, est un autre facteur contribuant aux performances de chargement. Si HTTPS est utilisé, ce processus inclura également l'heure de la négociation TLS. La phase de connexion se compose de trois temps:

  • connectStart correspond au moment où le navigateur commence à ouvrir une connexion à un serveur Web.
  • secureConnectionStart indique le moment où le client commence la négociation TLS.
  • connectEnd correspond au moment où la connexion au serveur Web a été établie.

La mesure de la durée totale de la connexion est semblable à la mesure de la durée totale de la résolution DNS: vous soustractez l'heure de début de l'heure de fin. Toutefois, il existe une propriété secureConnectionStart supplémentaire qui peut être 0 si HTTPS n'est pas utilisé ou si la connexion est persistante. Si vous souhaitez mesurer le temps de négociation TLS, gardez cela à l'esprit:

// 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;
}

Une fois la recherche DNS et la négociation de la connexion terminées, les délais liés à l'extraction des documents et de leurs ressources dépendantes entrent en jeu.

Requêtes et réponses

Les performances de chargement sont affectées par deux types de facteurs:

  • Facteurs externes:latence et bande passante, par exemple. En dehors du choix d'une société d'hébergement et éventuellement d'un CDN, nous n'avons (presque) aucun contrôle sur ces éléments, car les utilisateurs peuvent accéder au Web de n'importe où.
  • Facteurs intrinsèques:il s'agit d'éléments tels que les architectures côté serveur et côté client, ainsi que la taille des ressources et notre capacité à les optimiser, qui sont sous notre contrôle.

Ces deux types de facteurs ont un impact sur les performances de chargement. Les délais associés à ces facteurs sont essentiels, car ils décrivent la durée de téléchargement des ressources. Navigation Timing et Resource Timing décrivent les performances de chargement à l'aide des métriques suivantes:

  • fetchStart indique le moment où le navigateur commence à extraire une ressource (temps de latence des ressources) ou un document pour une requête de navigation (temps de latence de la navigation). Cette étape précède la requête réelle et correspond au moment où le navigateur vérifie les caches (par exemple, les instances HTTP et Cache).
  • workerStart indique le moment où une requête commence à être gérée dans le gestionnaire d'événements fetch d'un service worker. La valeur sera 0 si aucun service worker ne contrôle la page actuelle.
  • requestStart correspond au moment où le navigateur envoie la requête.
  • responseStart correspond au moment où le premier octet de la réponse arrive.
  • responseEnd correspond au moment où le dernier octet de la réponse arrive.

Ces délais vous permettent de mesurer plusieurs aspects des performances de chargement, tels que la recherche dans le cache dans un service worker et le temps de téléchargement:

// 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;
}

Vous pouvez également mesurer d'autres aspects de la latence des requêtes et des réponses:

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;

Autres mesures que vous pouvez effectuer

Les propriétés Navigation Timing et Resource Timing sont utiles pour bien plus que ce que les exemples précédents décrivent. Voici d'autres situations avec des délais pertinents qui peuvent être intéressants à explorer:

  • Redirections de page:les redirections sont une source de latence négligée, en particulier les chaînes de redirection. La latence est ajoutée de plusieurs manières, telles que les sauts HTTP vers HTTPS, ainsi que les redirections 302/301 non mises en cache. Les temps redirectStart, redirectEnd et redirectCount sont utiles pour évaluer la latence de redirection.
  • Déchargement de documents:dans les pages qui exécutent du code dans un gestionnaire d'événements unload, le navigateur doit exécuter ce code avant de pouvoir accéder à la page suivante. unloadEventStart et unloadEventEnd mesurent le déchargement des documents.
  • Traitement des documents:le temps de traitement des documents peut ne pas avoir d'incidence, sauf si votre site Web envoie des charges utiles HTML très volumineuses. Si c'est votre cas, les délais domInteractive, domContentLoadedEventStart, domContentLoadedEventEnd et domComplete peuvent vous intéresser.

Obtenir des temps dans votre code

Tous les exemples présentés jusqu'à présent utilisent performance.getEntriesByType, mais il existe d'autres façons d'interroger le tampon d'entrée des performances, comme performance.getEntriesByName et performance.getEntries. Ces méthodes sont efficaces lorsque seule une analyse légère est nécessaire. Dans d'autres cas, cependant, ils peuvent entraîner une charge de travail excessive du thread principal en itérant sur un grand nombre d'entrées, voire en interrogeant à plusieurs reprises le tampon de performances pour trouver de nouvelles entrées.

L'approche recommandée pour collecter des entrées à partir du tampon d'entrée des performances consiste à utiliser un PerformanceObserver. PerformanceObserver écoute les entrées de performances et les fournit lorsqu'elles sont ajoutées au tampon:

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

Cette méthode de collecte des codes temporels peut sembler gênante par rapport à l'accès direct au tampon d'entrée des performances, mais il est préférable de relier le thread principal à une tâche qui ne remplit pas un objectif critique ni visible par l'utilisateur.

Appeler chez soi

Une fois que vous avez collecté tous les délais dont vous avez besoin, vous pouvez les envoyer à un terminal pour une analyse plus approfondie. Pour ce faire, vous pouvez utiliser navigator.sendBeacon ou fetch avec l'option keepalive définie. Les deux méthodes envoient une requête à un point de terminaison spécifié de manière non bloquante. La requête est mise en file d'attente de manière à survivre à la session de page actuelle si nécessaire:

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

Dans cet exemple, la chaîne JSON arrive dans une charge utile POST que vous pouvez décoder, traiter et stocker dans un backend d'application si nécessaire.

Conclusion

Une fois les métriques collectées, il vous appartient de déterminer comment analyser ces données sur le terrain. Lorsque vous analysez des données sur le terrain, vous devez respecter certaines règles générales pour vous assurer de tirer des conclusions pertinentes:

  • Évitez les moyennes, car elles ne sont pas représentatives de l'expérience d'un utilisateur donné et peuvent être faussées par des valeurs aberrantes.
  • Utilisez les centiles. Dans les ensembles de données de métriques de performances basées sur le temps, plus la valeur est faible, mieux c'est. Autrement dit, lorsque vous priorisez les percentiles faibles, vous ne vous intéressez qu'aux expériences les plus rapides.
  • Privilégiez la longue traîne de valeurs. Lorsque vous donnez la priorité aux expériences correspondant au 75e centile ou plus, vous privilégiez les expériences les plus lentes.

Ce guide n'a pas vocation à être une ressource exhaustive sur la navigation ou le chronométrage des ressources, mais un point de départ. Voici quelques ressources supplémentaires qui pourraient vous être utiles:

Grâce à ces API et aux données qu'elles fournissent, vous serez mieux à même de comprendre comment les performances de chargement sont perçues par les utilisateurs réels. Vous pourrez ainsi diagnostiquer et résoudre plus facilement les problèmes de performances de chargement sur le terrain.