Découvrez les bases de l'utilisation des API Navigation Timing et Resource Timing pour évaluer les performances de chargement sur le terrain.
Publié le 8 octobre 2021
Si vous avez utilisé la limitation de 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 génèrent des données de laboratoire et non des données de terrain.
Les tests synthétiques ne sont pas mauvais en soi, mais ils ne sont pas représentatifs de la vitesse de chargement de votre site Web pour les utilisateurs réels. Pour cela, vous avez besoin de données de terrain, que vous pouvez collecter à partir des API Navigation Timing et Resource Timing.
API pour vous aider à évaluer les performances de chargement sur le terrain
Navigation Timing et Resource Timing sont deux API similaires qui se chevauchent considérablement et qui mesurent deux choses distinctes :
- Navigation Timing mesure la vitesse des requêtes de documents HTML (c'est-à-dire les requêtes de navigation).
- Resource Timing mesure la vitesse des requêtes de ressources dépendantes du document, telles que les fichiers CSS, JavaScript, les images et d'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ée de performances. 'navigation' et 'resource' récupèrent respectivement les durées des API Navigation Timing et Resource Timing.
La quantité d'informations fournies par ces API peut être impressionnante, mais elles sont essentielles pour mesurer les performances de chargement sur le terrain, car vous pouvez collecter ces durées auprès des utilisateurs lorsqu'ils visitent votre site Web.
Cycle de vie et durées d'une requête réseau
La collecte et l'analyse des durées de navigation et des ressources s'apparentent à de l'archéologie, car vous reconstruisez la durée de vie éphémère d'une requête réseau après coup. Il est parfois utile de visualiser les concepts. Dans le cas des requêtes réseau, les outils pour les développeurs de votre navigateur peuvent vous aider.
Le cycle 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 durées sont représentées sous la forme d'un DOMHighResTimestamp. Selon votre navigateur, la granularité des durées peut être inférieure à la microseconde ou arrondie à la milliseconde. Vous devez examiner ces phases en détail et voir comment elles sont liées à 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 temps considérable, que vous devez mesurer sur le terrain. Navigation Timing et Resource Timing exposent deux durées liées au DNS :
domainLookupStartcorrespond au début de la résolution DNS.domainLookupEndcorrespond à la fin de la résolution DNS.
Pour calculer la durée totale de la résolution DNS, vous pouvez soustraire la métrique de début de la métrique de fin :
// Measuring DNS lookup time
const [pageNav] = performance.getEntriesByType('navigation');
const totalLookupTime = pageNav.domainLookupEnd - pageNav.domainLookupStart;
Négociation de connexion
Un autre facteur qui contribue aux performances de chargement est la négociation de connexion, qui correspond à la latence lors de la connexion à un serveur Web. Si HTTPS est impliqué, ce processus inclura également le temps de négociation TLS. La phase de connexion comporte trois durées :
connectStartcorrespond au moment où le navigateur commence à ouvrir une connexion à un serveur Web.secureConnectionStartindique le moment où le client commence la négociation TLS.connectEndcorrespond au moment où la connexion au serveur Web est établie.
La mesure du temps de connexion total est semblable à la mesure du temps total de résolution DNS : vous soustrayez la durée de début de la durée 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, vous devez en tenir compte :
// 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 résolution DNS et la négociation de connexion terminées, les durées liées à la récupération 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 extrinsèques : il s'agit de la latence et de la bande passante, par exemple. Au-delà du choix d'une entreprise d'hébergement et éventuellement d'un CDN, ces facteurs sont (pour la plupart) hors de notre contrôle, car les utilisateurs peuvent accéder au Web depuis n'importe où.
- Facteurs intrinsèques : il s'agit des architectures côté serveur et côté client, ainsi que de la taille des ressources et de notre capacité à les optimiser, qui sont sous notre contrôle.
Les deux types de facteurs affectent les performances de chargement. Les durées liées à ces facteurs sont essentielles, car elles décrivent le temps nécessaire au téléchargement des ressources. Navigation Timing et Resource Timing décrivent les performances de chargement avec les métriques suivantes :
fetchStartindique le moment où le navigateur commence à récupérer une ressource (Resource Timing) ou un document pour une requête de navigation (Navigation Timing). Cela précède la requête réelle et correspond au moment où le navigateur vérifie les caches (par exemple, les instances HTTP etCacheinstances).workerStartindique le moment où une requête commence à être gérée dans le gestionnaire d'événementsfetchd'un service worker. La valeur est0lorsqu'aucun service worker ne contrôle la page actuelle.requestStartcorrespond au moment où le navigateur effectue la requête.responseStartcorrespond au moment où le premier octet de la réponse arrive.responseEndcorrespond au moment où le dernier octet de la réponse arrive.
Ces durées vous permettent de mesurer plusieurs aspects des performances de chargement, tels que la recherche dans le cache d'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
Navigation Timing et Resource Timing sont utiles pour plus que ce que les exemples précédents décrivent. Voici d'autres situations avec des durées pertinentes qui peuvent être intéressantes à explorer :
- Redirections de page : les redirections sont une source de latence ajoutée souvent négligée, en particulier les chaînes de redirection. La latence est ajoutée de plusieurs manières, par exemple via des sauts HTTP vers HTTPS, ainsi que des redirections 302/301 non mises en cache. Les durées
redirectStart,redirectEndetredirectCountsont utiles pour évaluer la latence de redirection. - Déchargement de documents : sur les pages qui exécutent du code dans un
unloadgestionnaire d'événements, le navigateur doit exécuter ce code avant de pouvoir accéder à la page suivante.unloadEventStartetunloadEventEndmesurent le déchargement des documents. - Traitement des documents : le temps de traitement des documents peut ne pas être important, sauf si votre site Web envoie des charges utiles HTML très volumineuses. Si c'est le cas, les durées
domInteractive,domContentLoadedEventStart,domContentLoadedEventEndetdomCompletepeuvent vous intéresser.
Comment obtenir des durées 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 de performances, comme performance.getEntriesByName et performance.getEntries. Ces méthodes conviennent lorsqu'une analyse légère est nécessaire. Dans d'autres situations, elles peuvent toutefois introduire un travail excessif du thread principal en itérant sur un grand nombre d'entrées, voire en interrogeant de manière répétée 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 de 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 durées peut sembler étrange par rapport à l'accès direct au tampon d'entrée de performances, mais il est préférable de ne pas encombrer le thread principal avec un travail qui ne sert pas un objectif essentiel et visible par l'utilisateur.
Comment envoyer des données à un point de terminaison
Une fois que vous avez collecté toutes les durées dont vous avez besoin, vous pouvez les envoyer à un point de terminaison pour une analyse plus approfondie. Pour ce faire, vous pouvez utiliser navigator.sendBeacon ou un 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 que vous avez collecté des métriques, c'est à vous de déterminer comment analyser ces données de terrain. Lorsque vous analysez des données de terrain, vous devez respecter quelques règles générales pour vous assurer de tirer des conclusions significatives :
- Évitez les moyennes, car elles ne sont pas représentatives de l'expérience d'un utilisateur et peuvent être faussées par des valeurs aberrantes.
- Fiez-vous aux 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. Cela signifie que lorsque vous accordez la priorité aux centiles faibles, vous ne faites attention qu'aux expériences les plus rapides.
- Donnez la priorité à la longue traîne des valeurs. Lorsque vous accordez la priorité aux expériences au 75e centile ou plus, vous vous concentrez sur ce qui compte : les expériences les plus lentes.
Ce guide n'est pas destiné à être une ressource exhaustive sur Navigation Timing ou Resource Timing, mais plutôt un point de départ. Voici quelques ressources supplémentaires qui peuvent vous être utiles :
- Spécification Navigation Timing.
- Spécification Resource Timing
- ResourceTiming en pratique.
- API Navigation Timing (MDN)
- API Resource Timing (MDN)
Grâce à ces API et aux données qu'elles fournissent, vous serez mieux équipé pour comprendre comment les utilisateurs réels perçoivent les performances de chargement. Vous pourrez ainsi diagnostiquer et résoudre plus facilement les problèmes de performances de chargement sur le terrain.