Les métriques centrées sur l'utilisateur que vous pouvez mesurer de manière universelle sur n'importe quel site Web sont très utiles. Ces métriques vous permettent :
- Comprendre comment les utilisateurs réels perçoivent le Web dans son ensemble.
- Comparez votre site à celui d'un concurrent.
- Suivez des données utiles et exploitables dans vos outils d'analyse sans avoir à écrire de code personnalisé.
Les métriques universelles constituent une bonne base, mais dans de nombreux cas, vous devez mesurer plus que ces métriques pour capturer l'expérience complète de votre site.
Les métriques personnalisées vous permettent de mesurer des aspects de l'expérience sur votre site qui ne s'appliquent qu'à celui-ci, par exemple :
- Temps nécessaire à une application monopage (SPA) pour passer d'une "page" à une autre.
- Temps nécessaire pour qu'une page affiche les données extraites d'une base de données pour les utilisateurs connectés.
- Temps nécessaire pour qu'une application avec rendu côté serveur (SSR) s'hydrate.
- Taux de succès du cache pour les ressources chargées par les visiteurs connus.
- Latence des événements de clic ou de clavier dans un jeu.
API permettant de mesurer des métriques personnalisées
Historiquement, les développeurs Web ne disposaient pas de nombreuses API de bas niveau pour mesurer les performances. Ils devaient donc recourir à des astuces pour déterminer si un site était performant.
Par exemple, il est possible de déterminer si le thread principal est bloqué en raison de tâches JavaScript de longue durée en exécutant une boucle requestAnimationFrame et en calculant le delta entre chaque frame. Si le delta est beaucoup plus long que la fréquence d'images de l'écran, vous pouvez le signaler comme une tâche longue. Toutefois, ces astuces ne sont pas recommandées, car elles affectent elles-mêmes les performances (en déchargeant la batterie, par exemple).
La première règle d'une mesure efficace des performances est de s'assurer que vos techniques de mesure des performances ne causent pas elles-mêmes de problèmes de performances. Par conséquent, pour toutes les métriques personnalisées que vous mesurez sur votre site, il est préférable d'utiliser l'une des API suivantes, si possible.
API Performance Observer
L'API Performance Observer est le mécanisme qui collecte et affiche les données de toutes les autres API de performances présentées sur cette page. Il est essentiel de le comprendre pour obtenir des données de qualité.
Vous pouvez utiliser PerformanceObserver pour vous abonner passivement aux événements liés aux performances. Cela permet aux rappels d'API de se déclencher pendant les périodes d'inactivité, ce qui signifie qu'ils n'interfèrent généralement pas avec les performances de la page.
Pour créer un PerformanceObserver, transmettez-lui un rappel à exécuter chaque fois que de nouvelles entrées de performances sont distribuées. Ensuite, vous indiquez à l'observateur les types d'entrées à écouter à l'aide de la méthode observe() :
const po = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
// Log the entry and all associated details.
console.log(entry.toJSON());
}
});
po.observe({type: 'some-entry-type'});
Les sections suivantes listent tous les types d'entrées disponibles pour l'observation. Toutefois, dans les navigateurs plus récents, vous pouvez inspecter les types d'entrées disponibles via la propriété statique PerformanceObserver.supportedEntryTypes.
Observer les entrées déjà effectuées
Par défaut, les objets PerformanceObserver ne peuvent observer les entrées qu'au moment où elles se produisent. Cela peut poser problème si vous souhaitez charger votre code d'analyse des performances de manière différée afin qu'il ne bloque pas les ressources de priorité plus élevée.
Pour obtenir des entrées historiques (après qu'elles se sont produites), définissez le flag buffered sur true lorsque vous appelez observe(). La première fois que votre rappel PerformanceObserver est appelé, le navigateur inclut les entrées historiques de son tampon d'entrées de performances, jusqu'à la taille maximale du tampon pour ce type.
po.observe({
type: 'some-entry-type',
buffered: true,
});
Anciennes API de performances à éviter
Avant l'API Performance Observer, les développeurs pouvaient accéder aux entrées de performances à l'aide des trois méthodes suivantes définies sur l'objet performance :
Bien que ces API soient toujours compatibles, nous vous déconseillons de les utiliser, car elles ne vous permettent pas d'écouter l'émission de nouvelles entrées. De plus, de nombreuses nouvelles API (telles que largest-contentful-paint) ne sont pas exposées via l'objet performance, mais uniquement via PerformanceObserver.
À moins que vous n'ayez spécifiquement besoin de la compatibilité avec Internet Explorer, il est préférable d'éviter ces méthodes dans votre code et d'utiliser PerformanceObserver à l'avenir.
API User Timing
L'API User Timing est une API de mesure à usage général pour les métriques temporelles. Il vous permet de marquer arbitrairement des points dans le temps, puis de mesurer la durée entre ces marques ultérieurement.
// Record the time immediately before running a task.
performance.mark('myTask:start');
await doMyTask();
// Record the time immediately after running a task.
performance.mark('myTask:end');
// Measure the delta between the start and end of the task
performance.measure('myTask', 'myTask:start', 'myTask:end');
Bien que des API telles que Date.now() ou performance.now() vous offrent des fonctionnalités similaires, l'avantage d'utiliser l'API User Timing est qu'elle s'intègre bien aux outils de performances. Par exemple, les Outils pour les développeurs Chrome visualisent les mesures User Timing dans le panneau "Performances". De nombreux fournisseurs d'analyses suivront également automatiquement toutes les mesures que vous effectuez et enverront les données de durée à leur backend d'analyse.
Pour générer des rapports sur les mesures User Timing, vous pouvez utiliser PerformanceObserver et vous inscrire pour observer les entrées de type measure :
// Create the performance observer.
const po = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
// Log the entry and all associated details.
console.log(entry.toJSON());
}
});
// Start listening for `measure` entries to be dispatched.
po.observe({type: 'measure', buffered: true});
API Long Tasks
L'API Long Tasks est utile pour savoir quand le thread principal du navigateur est bloqué suffisamment longtemps pour affecter la fréquence d'images ou la latence d'entrée. L'API signalera toutes les tâches qui s'exécutent pendant plus de 50 millisecondes.
Chaque fois que vous devez exécuter du code coûteux ou charger et exécuter des scripts volumineux, il est utile de vérifier si ce code bloque le thread principal. En fait, de nombreuses métriques de niveau supérieur sont elles-mêmes basées sur l'API Long Tasks (comme Time to Interactive (TTI) et Total Blocking Time (TBT)).
Pour déterminer quand les tâches longues se produisent, vous pouvez utiliser PerformanceObserver et vous inscrire pour observer les entrées de type longtask :
// Create the performance observer.
const po = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
// Log the entry and all associated details.
console.log(entry.toJSON());
}
});
// Start listening for `longtask` entries to be dispatched.
po.observe({type: 'longtask', buffered: true});
API Long Animation Frames
L'API Long Animation Frames est une nouvelle itération de l'API Long Tasks qui examine les frames longs (plutôt que les tâches longues) de plus de 50 millisecondes. Cela permet de résoudre certaines lacunes de l'API Long Tasks, y compris une meilleure attribution et une portée plus large des retards potentiellement problématiques.
Pour déterminer quand des longs frames se produisent, vous pouvez utiliser PerformanceObserver et vous inscrire pour observer les entrées de type long-animation-frame :
// Create the performance observer.
const po = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
// Log the entry and all associated details.
console.log(entry.toJSON());
}
});
// Start listening for `long-animation-frame` entries to be dispatched.
po.observe({type: 'long-animation-frame', buffered: true});
API Element Timing
La métrique Largest Contentful Paint (LCP) est utile pour savoir quand la plus grande image ou le plus grand bloc de texte ont été affichés à l'écran. Toutefois, dans certains cas, vous souhaitez mesurer le temps de rendu d'un autre élément.
Dans ce cas, utilisez l'API Element Timing. L'API LCP est en fait basée sur l'API Element Timing et ajoute un reporting automatique de l'élément avec le plus grand contenu, mais vous pouvez également générer des rapports sur d'autres éléments en leur ajoutant explicitement l'attribut elementtiming et en enregistrant un PerformanceObserver pour observer le type d'entrée element.
<img elementtiming="hero-image" />
<p elementtiming="important-paragraph">This is text I care about.</p>
<!-- ... -->
<script>
const po = new PerformanceObserver((entryList) => {
for (const entry of entryList.getEntries()) {
// Log the entry and all associated details.
console.log(entry.toJSON());
}
});
// Start listening for `element` entries to be dispatched.
po.observe({type: 'element', buffered: true});
</script>
API Event Timing
La métrique Interaction to Next Paint (INP) évalue la réactivité globale d'une page en observant toutes les interactions (clic, appui et clavier) tout au long de la durée de vie d'une page. L'INP d'une page correspond le plus souvent à l'interaction qui a mis le plus de temps à se terminer, depuis le moment où l'utilisateur a lancé l'interaction jusqu'à ce que le navigateur affiche le prochain frame montrant le résultat visuel de l'entrée de l'utilisateur.
La métrique INP est possible grâce à l'API Event Timing. Cette API expose un certain nombre de codes temporels qui se produisent au cours du cycle de vie de l'événement, y compris :
startTime: heure à laquelle le navigateur reçoit l'événement.processingStart: heure à laquelle le navigateur peut commencer à traiter les gestionnaires d'événements pour l'événement.processingEnd: heure à laquelle le navigateur termine d'exécuter tout le code synchrone initié à partir des gestionnaires d'événements pour cet événement.duration: temps (arrondi à 8 millisecondes pour des raisons de sécurité) entre le moment où le navigateur reçoit l'événement et le moment où il peut peindre le frame suivant après avoir terminé d'exécuter tout le code synchrone initié à partir des gestionnaires d'événements.
L'exemple suivant montre comment utiliser ces valeurs pour créer des mesures personnalisées :
const po = new PerformanceObserver((entryList) => {
// Get the last interaction observed:
const entries = Array.from(entryList.getEntries()).forEach((entry) => {
// Get various bits of interaction data:
const inputDelay = entry.processingStart - entry.startTime;
const processingTime = entry.processingEnd - entry.processingStart;
const presentationDelay = entry.startTime + entry.duration - entry.processingEnd;
const duration = entry.duration;
const eventType = entry.name;
const target = entry.target || "(not set)"
console.log("----- INTERACTION -----");
console.log(`Input delay (ms): ${inputDelay}`);
console.log(`Event handler processing time (ms): ${processingTime}`);
console.log(`Presentation delay (ms): ${presentationDelay}`);
console.log(`Total event duration (ms): ${duration}`);
console.log(`Event type: ${eventType}`);
console.log(target);
});
});
// A durationThreshold of 16ms is necessary to include more
// interactions, since the default is 104ms. The minimum
// durationThreshold is 16ms.
po.observe({type: 'event', buffered: true, durationThreshold: 16});
API Resource Timing
L'API Resource Timing fournit aux développeurs des informations détaillées sur la façon dont les ressources d'une page spécifique ont été chargées. Malgré le nom de l'API, les informations qu'elle fournit ne se limitent pas aux données de timing (bien qu'il y en ait beaucoup). Voici d'autres données auxquelles vous pouvez accéder :
initiatorType: la façon dont la ressource a été récupérée (par exemple, à partir d'une balise<script>ou<link>, ou d'un appelfetch()).nextHopProtocol: protocole utilisé pour récupérer la ressource, par exempleh2ouquic.encodedBodySize/decodedBodySize]: taille de la ressource sous sa forme encodée ou décodée (respectivement)transferSize: taille de la ressource réellement transférée sur le réseau. Lorsque les ressources sont fournies par le cache, cette valeur peut être beaucoup plus petite queencodedBodySizeet, dans certains cas, elle peut être nulle (si aucune revalidation du cache n'est requise).
Vous pouvez utiliser la propriété transferSize des entrées de timing des ressources pour mesurer une métrique de taux d'accès au cache ou une métrique de taille totale des ressources mises en cache. Cela peut être utile pour comprendre comment votre stratégie de mise en cache des ressources affecte les performances pour les visiteurs réguliers.
L'exemple suivant consigne toutes les ressources demandées par la page et indique si chacune d'elles a été fournie par le cache.
// Create the performance observer.
const po = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
// If transferSize is 0, the resource was fulfilled using the cache.
console.log(entry.name, entry.transferSize === 0);
}
});
// Start listening for `resource` entries to be dispatched.
po.observe({type: 'resource', buffered: true});
API Navigation Timing
L'API Navigation Timing est semblable à l'API Resource Timing, mais elle ne signale que les requêtes de navigation. Le type d'entrée navigation est également semblable au type d'entrée resource, mais il contient des informations supplémentaires spécifiques aux requêtes de navigation uniquement (par exemple, lorsque les événements DOMContentLoaded et load sont déclenchés).
De nombreux développeurs suivent une métrique pour comprendre le temps de réponse du serveur (Time to First Byte (TTFB)). Elle est disponible à l'aide de l'API Navigation Timing, plus précisément l'horodatage responseStart de l'entrée.
// Create the performance observer.
const po = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
// If transferSize is 0, the resource was fulfilled using the cache.
console.log('Time to first byte', entry.responseStart);
}
});
// Start listening for `navigation` entries to be dispatched.
po.observe({type: 'navigation', buffered: true});
Une autre métrique qui peut intéresser les développeurs qui utilisent un service worker est le temps de démarrage du service worker pour les requêtes de navigation. Il s'agit du temps nécessaire au navigateur pour démarrer le thread du service worker avant de pouvoir commencer à intercepter les événements de récupération.
Le temps de démarrage du service worker pour une requête de navigation spécifique peut être déterminé à partir de la différence entre entry.responseStart et entry.workerStart.
// Create the performance observer.
const po = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log('Service Worker startup time:',
entry.responseStart - entry.workerStart);
}
});
// Start listening for `navigation` entries to be dispatched.
po.observe({type: 'navigation', buffered: true});
API Server Timing
L'API Server Timing vous permet de transmettre des données de timing spécifiques aux requêtes de votre serveur au navigateur via les en-têtes de réponse. Par exemple, vous pouvez indiquer le temps nécessaire pour rechercher des données dans une base de données pour une requête spécifique. Cela peut être utile pour déboguer les problèmes de performances causés par la lenteur du serveur.
Pour les développeurs qui utilisent des fournisseurs d'analyse tiers, l'API Server Timing est le seul moyen de corréler les données sur les performances du serveur avec d'autres métriques commerciales que ces outils d'analyse peuvent mesurer.
Pour spécifier les données de timing du serveur dans vos réponses, vous pouvez utiliser l'en-tête de réponse Server-Timing. Voici un exemple.
HTTP/1.1 200 OK
Server-Timing: miss, db;dur=53, app;dur=47.2
Ensuite, à partir de vos pages, vous pouvez lire ces données sur les entrées resource ou navigation des API Resource Timing et Navigation Timing.
// Create the performance observer.
const po = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
// Logs all server timing data for this response
console.log('Server Timing', entry.serverTiming);
}
});
// Start listening for `navigation` entries to be dispatched.
po.observe({type: 'navigation', buffered: true});