MaĂźtrisez l'API Resource Timing pour diagnostiquer et optimiser la performance frontend. Apprenez Ă mesurer le temps de chargement de chaque ressource.
Optimiser la performance frontend : une analyse approfondie de l'API Resource Timing
Dans le monde du développement web, la vitesse n'est pas seulement une fonctionnalité ; c'est une exigence fondamentale pour une expérience utilisateur positive. Un site web qui se charge lentement peut entraßner des taux de rebond plus élevés, un engagement utilisateur plus faible et, finalement, un impact négatif sur les objectifs commerciaux. Bien que des outils comme Lighthouse et WebPageTest fournissent des diagnostics de haut niveau inestimables, ils représentent souvent un test unique et synthétique. Pour vraiment comprendre et optimiser la performance pour un public mondial, nous devons mesurer l'expérience des utilisateurs réels, sur leurs appareils, sur leurs réseaux. C'est là qu'intervient le Real User Monitoring (RUM), et l'un de ses outils les plus puissants est l'API Resource Timing.
Ce guide complet vous propose une analyse approfondie de l'API Resource Timing. Nous explorerons ce qu'elle est, comment l'utiliser et comment transformer ses données granulaires en informations exploitables qui peuvent considérablement améliorer la performance de chargement de votre application. Que vous soyez un ingénieur frontend expérimenté ou que vous commenciez tout juste votre parcours d'optimisation de la performance, cet article vous donnera les connaissances nécessaires pour disséquer et comprendre la performance réseau de chaque ressource de votre page.
Qu'est-ce que l'API Resource Timing ?
L'API Resource Timing est une API JavaScript intĂ©grĂ©e au navigateur qui fournit des donnĂ©es de synchronisation rĂ©seau dĂ©taillĂ©es pour chaque ressource qu'une page web tĂ©lĂ©charge. Voyez-la comme une lentille microscopique pour l'activitĂ© rĂ©seau de votre page. Pour chaque image, script, feuille de style, police et appel API (via `fetch` ou `XMLHttpRequest`), cette API capture un horodatage de haute rĂ©solution pour chaque Ă©tape de la requĂȘte rĂ©seau.
Elle fait partie d'une suite plus large d'API de performance, qui fonctionnent ensemble pour fournir une vue holistique de la performance de votre application. Tandis que l'API Navigation Timing se concentre sur le cycle de vie du document principal, l'API Resource Timing zoome sur toutes les ressources dépendantes que le document principal demande.
Pourquoi est-ce si important ?
- Granularité : Elle va au-delà d'une simple métrique de "temps de chargement de la page". Vous pouvez voir précisément combien de temps ont pris la recherche DNS, la connexion TCP et le téléchargement du contenu pour un script tiers spécifique ou une image principale essentielle.
- Données utilisateur réelles : Contrairement aux outils de laboratoire, cette API s'exécute dans les navigateurs de vos utilisateurs. Cela vous permet de collecter des données de performance à partir d'un large éventail de conditions réseau, d'appareils et de zones géographiques, vous donnant une image fidÚle de votre expérience utilisateur globale.
- Informations exploitables : En analysant ces données, vous pouvez identifier des goulots d'étranglement spécifiques. Un script d'analyse tiers est-il lent à se connecter ? Votre CDN est-il peu performant dans une certaine région ? Vos images sont-elles trop volumineuses ? L'API Resource Timing fournit les preuves nécessaires pour répondre à ces questions avec confiance.
L'anatomie du chargement d'une ressource : déconstruction de la chronologie
Le cĆur de l'API Resource Timing est l'objet `PerformanceResourceTiming`. Pour chaque ressource chargĂ©e, le navigateur crĂ©e un de ces objets, qui contient une mine d'informations sur la synchronisation et la taille. Pour comprendre ces objets, il est utile de visualiser le processus de chargement comme un diagramme en cascade, oĂč chaque Ă©tape suit la prĂ©cĂ©dente.
Détaillons les propriétés clés d'un objet `PerformanceResourceTiming`. Toutes les valeurs temporelles sont des horodatages de haute résolution mesurés en millisecondes depuis le début de la navigation de la page (`performance.timeOrigin`).
startTime -> fetchStart -> domainLookupStart -> domainLookupEnd -> connectStart -> connectEnd -> requestStart -> responseStart -> responseEnd
Propriétés de synchronisation clés
name: L'URL de la ressource. C'est votre identifiant principal.entryType: Une chaĂźne indiquant le type d'entrĂ©e de performance. Dans notre cas, ce sera toujours "resource".initiatorType: C'est incroyablement utile pour le dĂ©bogage. Cela vous indique comment la ressource a Ă©tĂ© demandĂ©e. Les valeurs courantes incluent 'img', 'link' (pour le CSS), 'script', 'css' (pour les ressources chargĂ©es depuis le CSS comme `@import`), 'fetch' et 'xmlhttprequest'.duration: Le temps total pris par la ressource, calculĂ© commeresponseEnd - startTime. C'est la mĂ©trique de plus haut niveau pour une seule ressource.startTime: L'horodatage juste avant que la rĂ©cupĂ©ration de la ressource ne commence.fetchStart: L'horodatage juste avant que le navigateur ne commence Ă rĂ©cupĂ©rer la ressource. Il peut vĂ©rifier les caches (cache HTTP, cache du Service Worker) avant de passer au rĂ©seau. Si la ressource est servie depuis un cache, la plupart des valeurs de synchronisation suivantes seront Ă zĂ©ro.domainLookupStart&domainLookupEnd: Celles-ci marquent le dĂ©but et la fin de la recherche DNS (Domain Name System). La durĂ©e (domainLookupEnd - domainLookupStart) est le temps nĂ©cessaire pour rĂ©soudre le nom de domaine en une adresse IP. Une valeur Ă©levĂ©e ici peut indiquer un fournisseur DNS lent.connectStart&connectEnd: Celles-ci marquent le dĂ©but et la fin de l'Ă©tablissement d'une connexion au serveur. Pour HTTP, il s'agit de la poignĂ©e de main Ă trois voies TCP. La durĂ©e (connectEnd - connectStart) est votre temps de connexion TCP.secureConnectionStart: Si la ressource est chargĂ©e via HTTPS, cet horodatage marque le dĂ©but de la poignĂ©e de main SSL/TLS. La durĂ©e (connectEnd - secureConnectionStart) vous indique combien de temps la nĂ©gociation du chiffrement a pris. Des poignĂ©es de main TLS lentes peuvent ĂȘtre le signe d'une mauvaise configuration du serveur ou d'une latence rĂ©seau.requestStart: L'horodatage juste avant que le navigateur n'envoie la requĂȘte HTTP rĂ©elle pour la ressource au serveur. Le temps entreconnectEndetrequestStartest souvent appelĂ© temps de "mise en file d'attente de la requĂȘte", oĂč le navigateur attend une connexion disponible.responseStart: L'horodatage lorsque le navigateur reçoit le tout premier octet de la rĂ©ponse du serveur. La durĂ©e (responseStart - requestStart) est le fameux Time to First Byte (TTFB). Un TTFB Ă©levĂ© est presque toujours un indicateur d'un processus backend lent ou d'une latence cĂŽtĂ© serveur.responseEnd: L'horodatage lorsque le dernier octet de la ressource a Ă©tĂ© reçu, fermant avec succĂšs la requĂȘte. La durĂ©e (responseEnd - responseStart) reprĂ©sente le temps de tĂ©lĂ©chargement du contenu.
Propriétés de taille de la ressource
Comprendre la taille de la ressource est tout aussi important que de comprendre la synchronisation. L'API fournit trois métriques clés :
transferSize: La taille en octets de la ressource transfĂ©rĂ©e sur le rĂ©seau, y compris les en-tĂȘtes et le corps de la rĂ©ponse compressĂ©. Si la ressource a Ă©tĂ© servie depuis un cache, cette valeur sera souvent 0. C'est le nombre qui a un impact direct sur le forfait de donnĂ©es de l'utilisateur et le temps rĂ©seau.encodedBodySize: La taille en octets du corps de la charge utile *aprĂšs* compression (par ex., Gzip ou Brotli) mais *avant* dĂ©compression. Cela vous aide Ă comprendre la taille de la charge utile elle-mĂȘme, sĂ©parĂ©ment des en-tĂȘtes.decodedBodySize: La taille en octets du corps de la charge utile dans sa forme originale non compressĂ©e. La comparaison avecencodedBodySizerĂ©vĂšle l'efficacitĂ© de votre stratĂ©gie de compression. Si ces deux nombres sont trĂšs proches pour un actif textuel (comme JS, CSS ou HTML), votre compression ne fonctionne probablement pas correctement.
Server Timing
L'une des intĂ©grations les plus puissantes avec l'API Resource Timing est la propriĂ©tĂ© `serverTiming`. Votre backend peut envoyer des mĂ©triques de performance dans un en-tĂȘte HTTP spĂ©cial (`Server-Timing`), et ces mĂ©triques apparaĂźtront dans le tableau `serverTiming` sur l'objet `PerformanceResourceTiming` correspondant. Cela comble le fossĂ© entre la surveillance des performances frontend et backend, vous permettant de voir les temps de requĂȘte de base de donnĂ©es ou les dĂ©lais de traitement de l'API directement dans vos donnĂ©es frontend.
Par exemple, un backend pourrait envoyer cet en-tĂȘte :
Server-Timing: db;dur=53, api;dur=47.2, cache;desc="HIT"
Ces données seraient disponibles dans la propriété `serverTiming`, vous permettant de corréler un TTFB élevé avec un processus lent spécifique sur le backend.
Comment accéder aux données de l'API Resource Timing en JavaScript
Maintenant que nous comprenons les données disponibles, voyons les moyens pratiques de les collecter en utilisant JavaScript. Il existe deux méthodes principales.
Méthode 1 : `performance.getEntriesByType('resource')`
C'est la façon la plus simple de commencer. Cette méthode renvoie un tableau de tous les objets `PerformanceResourceTiming` pour les ressources qui ont déjà fini de se charger sur la page au moment de l'appel.
// Attendre que la page se charge pour s'assurer que la plupart des ressources sont capturées
window.addEventListener('load', () => {
const resources = performance.getEntriesByType('resource');
resources.forEach((resource) => {
console.log(`Ressource chargée : ${resource.name}`);
console.log(` - Temps total : ${resource.duration.toFixed(2)}ms`);
console.log(` - Initiateur : ${resource.initiatorType}`);
console.log(` - Taille de transfert : ${resource.transferSize} octets`);
});
});
Limitation : Cette méthode est un instantané. Si vous l'appelez trop tÎt, vous manquerez les ressources qui ne se sont pas encore chargées. Si votre application charge dynamiquement des ressources longtemps aprÚs le chargement initial de la page, vous devriez interroger cette méthode à plusieurs reprises, ce qui est inefficace.
Méthode 2 : `PerformanceObserver` (L'approche recommandée)
Le `PerformanceObserver` est un moyen plus moderne, robuste et performant de collecter les entrées de performance. Au lieu que vous interrogiez les données, le navigateur pousse les nouvelles entrées vers votre fonction de rappel (callback) de l'observateur dÚs qu'elles sont disponibles.
Voici pourquoi c'est mieux :
- Asynchrone : Il ne bloque pas le thread principal.
- Complet : Il peut capturer les entrĂ©es dĂšs le tout dĂ©but du chargement de la page, Ă©vitant les conditions de concurrence oĂč un script s'exĂ©cute aprĂšs qu'une ressource a dĂ©jĂ Ă©tĂ© chargĂ©e.
- Efficace : Il évite le besoin d'interrogation avec `setTimeout` ou `setInterval`.
Voici une implémentation standard :
try {
const observer = new PerformanceObserver((list) => {
list.getEntries().forEach((entry) => {
// Traiter chaque entrée de ressource au fur et à mesure qu'elle arrive
if (entry.entryType === 'resource') {
console.log(`Ressource observée : ${entry.name}`);
console.log(` - Time to First Byte (TTFB) : ${(entry.responseStart - entry.requestStart).toFixed(2)}ms`);
}
});
});
// Commencer à observer les entrées de type 'resource'.
// Le drapeau 'buffered' garantit que nous obtenons les entrées chargées avant la création de notre observateur.
observer.observe({ type: 'resource', buffered: true });
// Vous pouvez arrĂȘter d'observer plus tard si nĂ©cessaire
// observer.disconnect();
} catch (e) {
console.error('PerformanceObserver n\'est pas pris en charge dans ce navigateur.');
}
L'option buffered: true est essentielle. Elle indique à l'observateur de distribuer immédiatement toutes les entrées `resource` qui sont déjà dans le tampon de performance du navigateur, vous assurant d'obtenir une liste complÚte depuis le début.
Gérer le tampon de performance
Les navigateurs ont une limite par défaut sur le nombre d'entrées de synchronisation de ressources qu'ils stockent (généralement 150). Sur des pages trÚs complexes, ce tampon peut se remplir. Lorsque cela se produit, le navigateur déclenche un événement `resourcetimingbufferfull`, et aucune nouvelle entrée n'est ajoutée.
Vous pouvez gérer cela en :
- Augmentant la taille du tampon : Utilisez `performance.setResourceTimingBufferSize(limit)` pour définir une limite plus élevée, par exemple, 300.
- Vidant le tampon : Utilisez `performance.clearResourceTimings()` aprÚs avoir traité les entrées pour faire de la place pour de nouvelles.
performance.addEventListener('resourcetimingbufferfull', () => {
console.warn('Le tampon de Resource Timing est plein. Vidage en cours...');
// Traitez d'abord les entrées existantes de votre observateur
// Ensuite, videz le tampon
performance.clearResourceTimings();
// Vous devrez peut-ĂȘtre rĂ©ajuster la taille du tampon si cela se produit frĂ©quemment
// performance.setResourceTimingBufferSize(500);
});
Cas d'usage pratiques et informations exploitables
La collecte de données n'est que la premiÚre étape. La vraie valeur réside dans la transformation de ces données en améliorations concrÚtes. Explorons quelques problÚmes de performance courants et comment l'API Resource Timing vous aide à les résoudre.
Cas d'usage 1 : Identifier les scripts tiers lents
Le problĂšme : Les scripts tiers pour l'analytique, la publicitĂ©, les widgets de support client et les tests A/B sont des tueurs de performance notoires. Ils peuvent ĂȘtre lents Ă charger, bloquer le rendu et mĂȘme causer de l'instabilitĂ©.
La solution : Utilisez l'API Resource Timing pour isoler et mesurer l'impact de ces scripts sur vos utilisateurs réels.
const observer = new PerformanceObserver((list) => {
const thirdPartyScripts = list.getEntries().filter(entry =>
entry.initiatorType === 'script' &&
!entry.name.startsWith(window.location.origin)
);
thirdPartyScripts.forEach(script => {
if (script.duration > 200) { // Définir un seuil, par ex., 200ms
console.warn(`Script tiers lent détecté : ${script.name}`, {
duration: `${script.duration.toFixed(2)}ms`,
transferSize: `${script.transferSize} octets`
});
// Dans un véritable outil RUM, vous enverriez ces données à votre backend d'analyse.
}
});
});
observer.observe({ type: 'resource', buffered: true });
Informations exploitables :
- DurĂ©e Ă©levĂ©e : Si un script a constamment une longue durĂ©e, demandez-vous s'il est vraiment nĂ©cessaire. Sa fonctionnalitĂ© peut-elle ĂȘtre remplacĂ©e par une alternative plus performante ?
- StratĂ©gie de chargement : Le script est-il chargĂ© de maniĂšre synchrone ? Utilisez les attributs `async` ou `defer` sur la balise `<script>` pour l'empĂȘcher de bloquer le rendu de la page.
- HĂ©berger sĂ©lectivement : Le script peut-il ĂȘtre chargĂ© de maniĂšre conditionnelle, uniquement sur les pages oĂč il est absolument nĂ©cessaire ?
Cas d'usage 2 : Optimiser la livraison des images
Le problÚme : Les images volumineuses et non optimisées sont l'une des causes les plus courantes de lenteur de chargement des pages, en particulier sur les appareils mobiles à bande passante limitée.
La solution : Filtrez les entrées de ressources par `initiatorType: 'img'` et analysez leur taille et leurs temps de chargement.
// ... à l'intérieur d'un callback PerformanceObserver ...
list.getEntries()
.filter(entry => entry.initiatorType === 'img')
.forEach(image => {
const downloadTime = image.responseEnd - image.responseStart;
// Une grande image peut avoir un temps de téléchargement élevé et une grande transferSize
if (downloadTime > 500 || image.transferSize > 100000) { // 500ms ou 100KB
console.log(`ProblĂšme potentiel d'image volumineuse : ${image.name}`, {
downloadTime: `${downloadTime.toFixed(2)}ms`,
transferSize: `${(image.transferSize / 1024).toFixed(2)} KB`
});
}
});
Informations exploitables :
- `transferSize` et `downloadTime` élevés : C'est un signal clair que l'image est trop grande. Optimisez-la en utilisant des formats modernes comme WebP ou AVIF, en la compressant de maniÚre appropriée et en la redimensionnant à ses dimensions affichées.
- Utiliser `srcset` : ImplĂ©mentez des images rĂ©actives en utilisant l'attribut `srcset` pour servir diffĂ©rentes tailles d'image en fonction de la fenĂȘtre d'affichage de l'utilisateur.
- Lazy Loading : Pour les images en dessous de la ligne de flottaison, utilisez `loading="lazy"` pour différer leur chargement jusqu'à ce que l'utilisateur les fasse défiler dans la vue.
Cas d'usage 3 : Diagnostiquer les goulots d'étranglement réseau
Le problĂšme : Parfois, le problĂšme n'est pas la ressource elle-mĂȘme mais le chemin rĂ©seau pour y accĂ©der. Un DNS lent, des connexions latentes ou des serveurs surchargĂ©s peuvent tous dĂ©grader la performance.
La solution : Décomposez la `duration` en ses phases constitutives pour identifier la source du retard.
function analyzeNetworkPhases(resource) {
const dnsTime = resource.domainLookupEnd - resource.domainLookupStart;
const tcpTime = resource.connectEnd - resource.connectStart;
const ttfb = resource.responseStart - resource.requestStart;
const downloadTime = resource.responseEnd - resource.responseStart;
console.log(`Analyse pour ${resource.name}`);
if (dnsTime > 50) console.warn(` - Temps DNS élevé : ${dnsTime.toFixed(2)}ms`);
if (tcpTime > 100) console.warn(` - Temps de connexion TCP élevé : ${tcpTime.toFixed(2)}ms`);
if (ttfb > 300) console.warn(` - TTFB élevé (serveur lent) : ${ttfb.toFixed(2)}ms`);
if (downloadTime > 500) console.warn(` - Téléchargement de contenu lent : ${downloadTime.toFixed(2)}ms`);
}
// ... appelez analyzeNetworkPhases(entry) dans votre observateur ...
Informations exploitables :
- Temps DNS Ă©levĂ© : Votre fournisseur DNS est peut-ĂȘtre lent. Envisagez de passer Ă un fournisseur mondial plus rapide. Vous pouvez Ă©galement utiliser `` pour rĂ©soudre le DNS des domaines tiers critiques Ă l'avance.
- Temps TCP élevé : Cela indique une latence dans l'établissement de la connexion. Un réseau de distribution de contenu (CDN) peut réduire cela en servant les actifs depuis un emplacement géographiquement plus proche de l'utilisateur. L'utilisation de `` peut effectuer à la fois la recherche DNS et la poignée de main TCP à l'avance.
- TTFB Ă©levĂ© : Cela pointe vers un backend lent. Travaillez avec votre Ă©quipe backend pour optimiser les requĂȘtes de base de donnĂ©es, amĂ©liorer la mise en cache cĂŽtĂ© serveur ou mettre Ă niveau le matĂ©riel du serveur. L'en-tĂȘte `Server-Timing` est votre meilleur ami ici.
- Temps de téléchargement élevé : C'est une fonction de la taille de la ressource et de la bande passante du réseau. Optimisez l'actif (compressez, minifiez) ou utilisez un CDN pour améliorer le débit.
Limites et considérations
Bien qu'incroyablement puissante, l'API Resource Timing a quelques limitations importantes Ă connaĂźtre.
Ressources cross-origin et l'en-tĂȘte `Timing-Allow-Origin`
Pour des raisons de sécurité, les navigateurs restreignent les détails de synchronisation disponibles pour les ressources chargées depuis une origine différente (domaine, protocole ou port) de votre page principale. Par défaut, pour une ressource cross-origin, la plupart des propriétés de synchronisation comme `redirectStart`, `domainLookupStart`, `connectStart`, `requestStart`, `responseStart`, et les propriétés de taille comme `transferSize` seront à zéro.
Pour exposer ces dĂ©tails, le serveur hĂ©bergeant la ressource doit inclure l'en-tĂȘte HTTP `Timing-Allow-Origin` (TAO). Par exemple :
Timing-Allow-Origin: * (Permet à n'importe quelle origine de voir les détails de synchronisation)
Timing-Allow-Origin: https://www.your-website.com (N'autorise que votre site web)
Ceci est crucial lorsque vous travaillez avec vos propres CDN ou API sur diffĂ©rents sous-domaines. Assurez-vous qu'ils sont configurĂ©s pour envoyer l'en-tĂȘte TAO afin que vous puissiez obtenir une visibilitĂ© complĂšte des performances.
Support des navigateurs
L'API Resource Timing, y compris `PerformanceObserver`, est largement prise en charge par tous les navigateurs modernes (Chrome, Firefox, Safari, Edge). Cependant, pour les navigateurs plus anciens, elle peut ne pas ĂȘtre disponible. Encadrez toujours votre code dans un bloc `try...catch` ou vĂ©rifiez l'existence de `window.PerformanceObserver` avant de l'utiliser pour Ă©viter les erreurs sur les clients hĂ©ritĂ©s.
Conclusion : des données à la décision
L'API Resource Timing est un instrument essentiel dans la boßte à outils du développeur web moderne. Elle démystifie la cascade réseau, fournissant les données brutes et granulaires nécessaires pour passer de plaintes vagues comme "le site est lent" à des diagnostics précis et basés sur des données comme "notre widget de chat tiers a un TTFB de 400 ms pour les utilisateurs en Asie du Sud-Est."
En tirant parti de `PerformanceObserver` pour collecter des données utilisateur réelles et en analysant le cycle de vie complet de chaque ressource, vous pouvez :
- Tenir les fournisseurs tiers responsables de leurs performances.
- Valider l'efficacité de votre CDN et de vos stratégies de mise en cache à travers le monde.
- Trouver et corriger les images surdimensionnées et les actifs non optimisés.
- Corréler les retards frontend avec les temps de traitement backend.
Le chemin vers un web plus rapide est continu. Commencez dĂšs aujourd'hui. Ouvrez la console de dĂ©veloppement de votre navigateur, exĂ©cutez les extraits de code de cet article sur votre propre site, et commencez Ă explorer les riches donnĂ©es de performance qui vous attendaient depuis le dĂ©but. En mesurant ce qui compte, vous pouvez construire des expĂ©riences plus rapides, plus rĂ©silientes et plus agrĂ©ables pour tous vos utilisateurs, oĂč qu'ils soient dans le monde.