Il est très utile de disposer de métriques centrées sur l'utilisateur que vous pouvez mesurer de manière universelle sur n'importe quel site Web. Grâce à ces métriques, vous pouvez:
- Comprendre comment les utilisateurs réels utilisent 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 référence, 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 peut-être qu'à votre site, par exemple :
- Le temps nécessaire pour qu'une application monopage (SPA) passe 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.
- Durée d'hydratation d'une application rendue côté serveur (SSR).
- Taux de réussite du cache pour les ressources chargées par les visiteurs connus.
- Latence des événements de clic ou de clavier dans un jeu.
API pour mesurer les métriques personnalisées
Par le passé, les développeurs Web n'avaient pas beaucoup d'API de bas niveau pour mesurer les performances. Ils ont donc dû recourir au piratage 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 considérablement plus long que le frame rate de l'écran, vous pouvez le signaler comme une tâche longue. Toutefois, ces piratages ne sont pas recommandés, car ils affectent les performances elles-mêmes (en vidant la batterie, par exemple).
La première règle d'une mesure efficace des performances consiste à s'assurer que vos techniques de mesure des performances ne provoquent 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, nous vous recommandons 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 abordé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'affectent généralement pas 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. Vous indiquez ensuite à 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 répertorient tous les types d'entrées disponibles à 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 qui se sont déjà produites
Par défaut, les objets PerformanceObserver
ne peuvent observer les entrées que lorsqu'elles se produisent. Cela peut entraîner des problèmes si vous souhaitez charger de manière différée votre code d'analyse des performances afin qu'il ne bloque pas les ressources de priorité plus élevée.
Pour obtenir les entrées de l'historique (après leur occurrence), définissez l'indicateur buffered
sur true
lorsque vous appelez observe()
. Le navigateur inclut les entrées historiques de son tampon d'entrée des performances la première fois que votre rappel PerformanceObserver
est appelé, jusqu'à la taille maximale de la mémoire 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, leur utilisation n'est pas recommandée, car elles ne vous permettent pas d'écouter quand de nouvelles entrées sont émises. De plus, de nombreuses nouvelles API (telles que largest-contentful-paint
) ne sont pas exposées via l'objet performance
. Elles ne le sont que 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 basées sur le temps. Il vous permet de marquer arbitrairement des points dans le temps, puis de mesurer la durée entre ces repères plus tard.
// 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 de l'API User Timing est qu'elle s'intègre bien aux outils de performances. Par exemple, les outils de développement Chrome visualisent les mesures de la durée de l'utilisateur dans le panneau "Performances". De nombreux fournisseurs d'analyse suivent également automatiquement toutes les mesures que vous effectuez et envoient les données de durée à leur backend d'analyse.
Pour générer des rapports sur les mesures du temps utilisateur, vous pouvez utiliser PerformanceObserver et vous inscrire pour observer des 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 permet de savoir quand le thread principal du navigateur est bloqué suffisamment longtemps pour affecter la fréquence d'images ou la latence de saisie. L'API signale 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 basées sur l'API Long Tasks elle-même (comme le temps de réponse (TTI) et le temps de blocage total (TBT)).
Pour déterminer quand des tâches longues se produisent, vous pouvez utiliser PerformanceObserver et vous enregistrer 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 résout certains des défauts de l'API Long Tasks, y compris une meilleure attribution et une couverture plus large des retards potentiellement problématiques.
Pour déterminer quand des frames longs se produisent, vous pouvez utiliser PerformanceObserver et vous enregistrer 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 le plus grand bloc d'image ou de texte a été affiché à l'écran. Toutefois, dans certains cas, vous souhaitez mesurer le délai 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 des rapports automatiques sur l'élément le plus volumineux. Vous pouvez toutefois également créer 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 de la page en observant toutes les interactions liées aux clics, aux appuis et au clavier tout au long de la page. L'INP d'une page correspond le plus souvent à l'interaction la plus longue, du moment où l'utilisateur a initié l'interaction jusqu'au moment où le navigateur peint le frame suivant affichant le résultat visuel de l'entrée 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 l'exécution de 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é) écoulé entre le moment où le navigateur reçoit l'événement et celui où il peut peindre le frame suivant après avoir terminé l'exécution de tout le code synchrone lancé à 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 le chargement des ressources d'une page spécifique. Malgré le nom de l'API, les informations qu'elle fournit ne se limitent pas aux données temporelles (bien qu'il y en ait beaucoup). Voici d'autres données auxquelles vous pouvez accéder :
initiatorType
: manière dont la ressource a été récupérée, par exemple à partir d'un tag<script>
ou<link>
, ou d'un appelfetch()
.nextHopProtocol
: protocole utilisé pour extraire la ressource, par exempleh2
ouquic
.encodedBodySize
/decodedBodySize : taille de la ressource sous forme encodée ou décodée (respectivement)transferSize
: taille de la ressource effectivement transférée sur le réseau. Lorsque les ressources sont satisfaites par le cache, cette valeur peut être beaucoup plus faible queencodedBodySize
, et 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 temps de ressource pour mesurer une métrique de taux de succès de cache ou de taille totale de la ressource mise en cache, ce qui peut être utile pour comprendre l'impact de votre stratégie de mise en cache des ressources sur les performances des visiteurs récurrents.
L'exemple suivant consigne toutes les ressources demandées par la page et indique si chacune d'elles a été satisfaite par le cache ou non.
// 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 uniquement aux requêtes de navigation (par exemple, lorsque les événements DOMContentLoaded
et load
se déclenchent).
Une métrique que de nombreux développeurs suivent pour comprendre le temps de réponse du serveur (délai avant le premier octet (TTFB)) est disponible à l'aide de l'API Navigation Timing, en particulier le code temporel 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});
Les développeurs qui utilisent un service worker peuvent également s'intéresser au 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 qu'il ne puisse commencer à intercepter les événements de récupération.
Le temps de démarrage du service worker pour une requête de navigation particulière peut être déterminé à partir du delta 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 temporelles spécifiques à la requête de votre serveur au navigateur via des 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 particulière, ce qui 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 mettre en corrélation les données de performances du serveur avec d'autres métriques métier que ces outils d'analyse peuvent mesurer.
Pour spécifier des données de temporisation 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 dans 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});