Une application peut être fonctionnelle et bien structurée, mais tout de même rester pénible à utiliser dûs à des problèmes de performance. Certains problèmes peuvent être corrigés tardivement, mais les corrections deviennent plus coûteuses lorsque l’application a été construite autour de dépendances lourdes ou d’une succession d’échanges réseau.
1. Mesurer et diagnostiquer avec DevTools
Une page peut sembler lente à cause d’une police demandée tardivement, d’une image surdimensionnée, d’un appel d’API en série ou d’une tâche JavaScript qui bloque le fil principal. Les instruments de DevTools montrent des dimensions différentes du même chargement.
- Le panneau Réseau (Network) explique ce qui voyage.
- Lighthouse fournit un bilan automatisé.
- Le panneau Performance montre ce que le navigateur fait avec les ressources reçues.
Le panneau Réseau : suivre les requêtes
Le panneau Network enregistre les requêtes effectuées par la page. Chaque ligne correspond à une ressource ou à un échange : document, feuille de style, script, image, police ou appel d’API. La cascade placée à droite montre quand la requête commence, combien de temps elle attend et à quel moment elle se termine.
Mesures à observer
- Status : la réponse a-t-elle réussi, été redirigée ou échoué ?
- Type : s’agit-il du document, de CSS, de JavaScript, d’une image, d’une police ou d’un appel
fetch? - Initiator : quelle ressource ou quelle ligne de code a provoqué la requête ?
- Size : combien d’octets ont réellement traversé le réseau et quelle est la taille de la ressource après décompression ?
- Time : combien de temps la requête a-t-elle pris au total ?
- Timing : où le temps a-t-il été dépensé — connexion, attente de la réponse ou téléchargement ?
- Priority : quelle importance le navigateur a-t-il accordée à la ressource ?
Lighthouse : obtenir un bilan automatisé
Lighthouse exécute une série d’audits dans des conditions contrôlées. Il fournit des métriques, détecte des problèmes fréquents et associe certaines recommandations aux ressources concernées. Il constitue un bon point de départ, mais il ne remplace pas l’analyse détaillée dans les panneaux Réseau et Performance.
Mesures à observer
- Largest Contentful Paint (LCP) : moment où le principal contenu visible est affiché ;
- Cumulative Layout Shift (CLS) : importance des déplacements visuels inattendus ;
- Total Blocking Time (TBT) : durée cumulée pendant laquelle de longues tâches empêchent le fil principal de répondre rapidement ;
- First Contentful Paint (FCP) : moment où un premier contenu est affiché ;
- les diagnostics portant sur les ressources bloquantes, le JavaScript inutilisé, les images, le travail du fil principal ou les ressources tierces.
Le panneau Performance : analyser le travail du navigateur
Le panneau Performance enregistre l’activité du navigateur pendant le chargement ou pendant une interaction. Il montre les requêtes, l’exécution du JavaScript, les recalculs de style, les mises en page, la peinture et la composition des images à l’écran.
Éléments à observer
- les marqueurs de chargement et le moment où l’élément LCP est affiché ;
- les longues tâches, qui occupent le fil principal suffisamment longtemps pour retarder une interaction ;
- la répartition entre Scripting, Rendering et Painting ;
- les appels de fonctions dans le Call tree ou la vue Bottom-up ;
- les séquences répétées
Recalculate Style → Layout → Paint; - les images par seconde et les interruptions lors d’une animation ou d’un défilement.
2. Choisir une stratégie d’optimisation
Les technologies changent rapidement, mais la plupart des interventions reposent sur quelques stratégies stables. Les reconnaitre permet de choisir une solution en fonction du problème observé plutôt que de collectionner des réglages.
- Éliminer ce qui n’apporte pas suffisamment de valeur.
- Réduire le poids ou la quantité de travail de ce qui demeure.
- Différer ce qui n’est pas encore nécessaire.
- Devancer ce qui sera bientôt nécessaire et découvert trop tard.
- Réutiliser une ressource, une donnée ou un résultat déjà obtenu.
- Rapprocher les ressources et les services de leurs utilisateurs.
L’ordre est une heuristique, non une règle absolue. Supprimer une ressource produit généralement un gain plus complet que la compresser. Une ressource indispensable ne peut toutefois pas être éliminée, et une ressource secondaire peut être plus efficacement différée que minutieusement réduite.
2.1 Éliminer
La ressource la plus rapide est celle qui n’est jamais demandée, et le calcul le plus rapide est celui qui n’est jamais effectué.
Éliminer par la conception
- retirer une fonctionnalité dont la valeur ne justifie pas son coût ;
- éviter une bibliothèque complète pour quelques opérations simples disponibles dans la plateforme ;
- limiter les scripts de mesure, widgets et services tiers ;
- réduire le nombre de polices, de variantes et d’images décoratives ;
- ne pas inclure dans une page le code d’une fonctionnalité qui n’y existe pas.
Éliminer manuellement dans le code
- supprimer les imports, fonctions et dépendances inutilisés ;
- importer une fonction précise plutôt qu’un ensemble complet lorsque la bibliothèque le permet ;
- remplacer une dépendance redondante par une fonction déjà présente dans le projet ;
- supprimer les polyfills destinés à des navigateurs qui ne sont plus pris en charge ;
- utiliser Coverage pour repérer un bundle ou une feuille de style largement inutilisé dans un scénario.
Le tree shaking
Le tree shaking, ou élagage, est une forme d’élimination automatique du code mort pendant la construction. L’outil analyse les imports et exports des modules, marque les éléments nécessaires au programme et exclut les portions qui ne contribuent pas au résultat final.
// math.js
export function addition(a, b) {
return a + b;
}
export function multiplication(a, b) {
return a * b;
}
// main.js
import { addition } from "./math.js";
console.log(addition(2, 3));
Dans un cas simple, la fonction multiplication peut être absente du bundle de production puisqu’elle n’est jamais importée.
Des outils de construction comme Vite, Rollup, webpack et esbuild appliquent ce type d’analyse pendant la production d’un bundle. Le résultat est généralement meilleur lorsque le code utilise des modules ES statiques et que les modules déclarent correctement leurs effets de bord.
2.2 Réduire
Ce qui demeure nécessaire doit transporter moins d’octets et demander aussi peu de travail que possible. Les techniques diffèrent selon le type de ressource.
Réduire le document HTML
- minifier la version de production ;
- activer la compression HTTP ;
- éviter de générer une structure DOM inutilement profonde ou répétitive ;
- ne pas intégrer de grandes données JSON dans le document lorsqu’elles peuvent être demandées ou calculées autrement ;
- produire rapidement le document sur le serveur.
Réduire le CSS
- minifier et compresser les feuilles de style ;
- supprimer les règles inutilisées ;
- éviter de charger globalement le style d’une section rarement visitée ;
- limiter les bibliothèques qui génèrent ou incluent de grandes quantités de CSS ;
- simplifier les sélecteurs lorsque la structure devient inutilement complexe.
Réduire le JavaScript
- minifier et compresser les fichiers de production ;
- appliquer le tree shaking ;
- remplacer une dépendance lourde par une solution plus ciblée ;
- limiter le JavaScript de tiers ;
- éviter d’envoyer plusieurs bibliothèques qui remplissent le même rôle ;
- construire pour les navigateurs réellement pris en charge afin de limiter les transformations et polyfills inutiles ;
- réduire les grandes constantes ou données incorporées dans les bundles.
Réduire les images
- produire une image aux dimensions proches de sa taille d’affichage ;
- choisir un format approprié, notamment WebP ou AVIF pour les photographies lorsque la compatibilité visée le permet ;
- utiliser SVG pour les illustrations qui s’y prêtent ;
- ajuster la qualité de compression ;
- retirer les métadonnées inutiles ;
- fournir plusieurs tailles avec
srcsetet laisser le navigateur choisir selon l’écran.
<img
src="/images/projet-800.webp"
srcset="
/images/projet-480.webp 480w,
/images/projet-800.webp 800w,
/images/projet-1200.webp 1200w
"
sizes="(max-width: 640px) 100vw, 800px"
width="1200"
height="800"
alt="Présentation du projet"
/>
Les attributs width et height ne réduisent pas le fichier, mais permettent au navigateur de réserver l’espace avant l’arrivée de l’image et limitent les déplacements visuels.
Réduire les polices
- préférer le format WOFF2 ;
- limiter les familles, styles et graisses ;
- produire des sous-ensembles de caractères lorsque le contexte le permet ;
- évaluer si une police variable remplace avantageusement plusieurs fichiers ;
- utiliser une police système ou de repli pour les usages qui ne justifient pas un téléchargement supplémentaire ;
- éviter une police d’icônes lorsqu’un petit ensemble de SVG suffit.
Réduire les réponses d’API
- paginer les longues collections ;
- retourner uniquement les champs nécessaires au scénario ;
- fournir un résumé lorsque l’interface n’a pas besoin des détails ;
- compresser les réponses JSON ;
- éviter les structures excessivement répétitives ;
- regrouper les informations qui sont toujours consommées ensemble, sans créer une réponse monolithique pour tous les écrans.
2.3 Différer
Différer consiste à reporter une ressource ou un traitement qui n’est pas nécessaire à l’affichage ou à l’interaction immédiate. Le chargement a lieu plus tard, lorsqu’un besoin devient probable ou réel.
Images et cadres chargés paresseusement
<img
src="/images/equipe.webp"
alt="L'équipe du projet"
width="1200"
height="800"
loading="lazy"
/>
<iframe
src="https://exemple.ca/carte"
title="Carte du secteur"
loading="lazy"
></iframe>
L’attribut loading="lazy" permet au navigateur de reporter le chargement jusqu’à ce que la ressource approche de la zone visible. Il ne devrait pas être appliqué à l’image principale visible dès l’ouverture de la page, puisque cette image doit au contraire être découverte et chargée rapidement.
Scripts classiques : chargement et exécution
| Inclusion | Téléchargement | Exécution | Ordre entre plusieurs scripts |
|---|---|---|---|
<script src="app.js"> |
l’analyse du document peut être interrompue | dès que le script est reçu | ordre du document |
<script defer src="app.js"> |
en parallèle de l’analyse | après l’analyse du document | ordre conservé |
<script async src="mesure.js"> |
en parallèle de l’analyse | dès que le script est prêt | ordre non garanti |
<script type="module" src="app.js"> |
en parallèle avec ses dépendances | différée par défaut | dépendances du graphe de modules |
defer convient généralement à des scripts qui dépendent du document complet ou les uns des autres. async convient mieux à un script indépendant, comme certains outils de mesure. Un module JavaScript est différé par défaut, mais ses imports peuvent former leur propre graphe de dépendances.
Découpage du code et imports dynamiques
Le code splitting place des parties de l’application dans des fichiers séparés, chargés seulement lorsque leur fonctionnalité est demandée.
import { lazy, Suspense } from "react";
const VueAdmin = lazy(() => import("./pages/VueAdmin.jsx"));
export function Administration() {
return (
<Suspense fallback={<p>Chargement…</p>}>
<VueAdmin />
</Suspense>
);
}
2.4 Devancer
Devancer consiste à commencer plus tôt une opération que le navigateur ou l’application découvrirait naturellement trop tard. Cette stratégie s’applique seulement lorsqu’on a de bonnes raisons de croire que la ressource sera nécessaire.
Indications de chargement dans le document
| Indication | Usage principal |
|---|---|
preload |
ressource importante pour la page courante, découverte trop tard |
modulepreload |
module JavaScript et, selon le navigateur, préparation de ses dépendances |
preconnect |
connexion anticipée vers une origine qui sera bientôt utilisée |
prefetch |
ressource probablement utile lors d’une navigation future |
<link
rel="preload"
href="/fonts/inter-latin.woff2"
as="font"
type="font/woff2"
crossorigin
/>
<link rel="modulepreload" href="/assets/editeur.js" />
<link rel="preconnect" href="https://api.exemple.ca" />
<link rel="prefetch" href="/assets/vue-projet.js" />
Une image importante peut également recevoir un indice de priorité :
<img
src="/images/accueil.webp"
width="1600"
height="900"
fetchpriority="high"
alt="Campus vu depuis l'entrée principale"
/>
fetchpriority="high" ne garantit pas un ordre absolu. Il fournit une information supplémentaire au navigateur, qui conserve la décision finale en fonction des autres ressources.
Devancer dans le code JavaScript
Une cascade peut être créée par le code lui-même :
const profil = await chargerProfil();
const projets = await chargerProjets();
Si les deux opérations sont indépendantes, elles peuvent commencer ensemble :
const [profil, projets] = await Promise.all([
chargerProfil(),
chargerProjets(),
]);
Autres stratégies courantes :
- précharger une route au survol, au focus ou lorsque le navigateur est momentanément disponible ;
- commencer une requête avant une transition visuelle ;
- faire remonter la récupération de données pour éviter qu’un composant parent puis ses enfants lancent des requêtes en série ;
- précharger une image suivante dans une galerie ;
- préparer une donnée probable sans bloquer l’affichage actuel.
2.5 Réutiliser
Réutiliser consiste à éviter un transfert, une lecture ou un calcul dont le résultat est déjà disponible et encore valide.
Au niveau du réseau, cette stratégie mène aux caches HTTP, aux CDN, aux caches applicatifs et aux caches serveur. Ces mécanismes sont étudiés séparément dans la page consacrée au cache et aux applications web progressives.
La réutilisation existe aussi dans le code :
- conserver une donnée déjà chargée tant qu’elle reste valide ;
- construire une table de correspondance une seule fois plutôt que rechercher répétitivement dans un tableau ;
- mémoriser le résultat d’un calcul coûteux lorsque ses entrées n’ont pas changé ;
- partager une même ressource ou un même module plutôt que produire des copies indépendantes ;
- éviter de recalculer une transformation identique à chaque rendu.
Mémoïsation dans React
useMemo peut conserver le résultat d’un calcul entre les rendus :
import { useMemo } from "react";
function ListeProjets({ projets, filtre }) {
const projetsVisibles = useMemo(
() => projets.filter((projet) => correspond(projet, filtre)),
[projets, filtre],
);
return projetsVisibles.map((projet) => (
<CarteProjet key={projet.id} projet={projet} />
));
}
2.6 Rapprocher
À défaut d’éliminer un échange, on peut réduire la distance et le nombre d’intermédiaires entre l’utilisateur et la ressource.
Un réseau de distribution de contenu, ou CDN, conserve des copies des fichiers statiques dans plusieurs points de présence. L’utilisateur reçoit alors le HTML, le CSS, le JavaScript ou les images depuis une infrastructure plus proche.
Pour les API et les bases de données, rapprocher peut signifier :
- choisir une région de déploiement proche des utilisateurs principaux ;
- placer l’API et sa base de données dans des régions compatibles ;
- éviter qu’une requête traverse inutilement plusieurs services ;
- utiliser un cache serveur pour les lectures répétitives ;
- limiter les origines externes et les connexions supplémentaires ;
- produire certaines données à l’étape de construction lorsqu’elles changent rarement.
La proximité ne corrige toutefois pas une longue cascade ni un traitement inefficace. Un serveur proche qui attend successivement trois autres services peut rester plus lent qu’un serveur légèrement plus éloigné qui répond directement.
3. Une méthode d’optimisation
Une démarche reproductible évite de modifier plusieurs facteurs sans savoir lequel a produit le gain.
- Définir un scénario. Par exemple : ouvrir la page d’accueil sur un téléphone simulé, afficher un projet ou filtrer une liste.
- Mesurer une référence. Conserver la cascade, les métriques et, au besoin, une trace Performance.
- Localiser le coût dominant. Réseau, poids, découverte tardive, JavaScript, rendu ou infrastructure.
- Choisir une stratégie. Éliminer, réduire, différer, devancer, réutiliser, limiter ou rapprocher.
- Modifier un facteur important. Éviter une série de microchangements impossibles à attribuer.
- Mesurer de nouveau dans les mêmes conditions. Vérifier le gain et les effets secondaires.
- Tester un autre scénario. Une optimisation de la première visite ne doit pas détériorer une interaction ou une navigation suivante.