7 Minutes read

Synthèse et exemples des bonnes pratiques.

Image généré par chatGPT

La performance web est un sujet vaste et en perpétuelle évolution. Elle a aussi une part de subjectivité, une part de ressenti. Par exemple, la perception de la réactivité d’un site n’est pas la même d’une personne à l’autre.
Dans ce contexte, il est difficile de définir des actions précises et arrêtées pour obtenir les meilleurs performances pour un site web.
Néanmoins, on peut donner quelques bonnes pratiques assez générales qui font les standards des sites web d’aujourd’hui.

Avec cet article mon optique est de donner une liste (non exhaustive) de bonnes pratiques pour vous donner toute les chances d’avoir un site web aux performances convenables. Il s’adresse plutôt aux débutants dans le web, mais les confirmés pourront peut-être apprendre un ou deux trucs !

Avant toute chose, quand on parle de performance, on doit forcément avoir des outils et des méthodes de mesure communs.
Je ne vais pas entrer dans le détail de ces outils et mesures dans cet article mais je peux vous conseiller le site WebPageTest qui permet de calculer le “First Contentful Paint”, “Largest Contentful Paint”, etc… Des valeurs qui vont vous permettre de savoir si votre site se trouve dans les standards de performance.

Pour plus de clarté, j’ai découpé les performances en 2 familles :

  • Les performances de chargement
    (requêtes http, premier affichage du site, tailles des images)
  • Les performances du code
    (bonnes pratiques, algorithmie, temps d’exécution)

Les performances de chargement coté client

  • Éviter les ressources sur plusieurs noms de domaines
    Chaque nom de domaine va demander un temps de résolution DNS.
    Ce temps correspond à la recherche et la connexion à l’adresse de son serveur d’hébergement à travers le réseau.
    Si vous n’utilisez qu’un seul nom de domaine (celui où est hébergé votre site), vous n’aurez qu’une fois ce temps de résolution à la première visite du site. Par contre, ce temps sera multiplié par le nombre de noms de domaine différents !
    C’est pourquoi il faut éviter d’utiliser des ressources partagées sur un nom de domaine (ou CDN) qui n’est pas le vôtre (exemple les CDN pour les fonts google). Mieux vaut télécharger la font sur votre domaine (ou votre propre CDN) dans ce cas-là.
    Si vous pouvez, il est bon d’utiliser un CDN car il permet de raccourcir ce temps de connexion en allant chercher les ressources au point le plus proche. Dans ce cas, il faut mettre toutes vos ressources dessus, mais pas une ressource sur un CDN et d’autres sur un autre.
  • Ne mettre dans la balise <head> que les ressources qui sont strictement nécessaires pour afficher le premier rendu de la page
    Toutes les ressources qui se trouvent dans le <head> sont dites bloquantes.
    C’est-à-dire que le premier rendu de la page ne se fera qu’après que toutes ces ressources soient chargées.
    Le plus souvent ce sera la CSS et les fonts qui auront besoin d’être dans votre <head>, car vous en avez besoin pour afficher la page avec le bon rendu graphique.
    Par contre le javascript peut être chargé en différé et peut être mis dans le <body> en dernière position. Vous pouvez aussi, de manière plus moderne, utiliser l’attribut “defer” ou “async” de la balise <script>.
<html>
<head>
<link href="styles.css" rel="stylesheet">
</head>
<body>
<div>Your page content</div>
<script src="main.js" type="text/javascript"></script>
</body>
</html>

ou

<html>
<head>
<link href="styles.css" rel="stylesheet">
<script defer src="main.js" type="text/javascript"></script>
</head>
<body>
<div>Your page content</div>
</body>
</html>
  • Optimisation des images et du code JS
    Il est important de compresser au mieux les images que vous utilisez. Il existe des algorithmes de compression plutôt performants pour le web.
    Utilisez aussi les images correspondant à la résolution de l’utilisateur avec l’attribut “srcset” des images et les “media queries”.
    Et n’oubliez pas de “minifier” votre code JS.
    Librairies utiles :
    sharp (Node.js) : redimensionne et compresse les images côté serveur.
    imagemin : compresse les images au moment du build.
  • Chargement adaptatif
    Vous pouvez aller plus loin en adaptant les fonctionnalités, animations ou tout autres parties en tenant compte des contraintes matérielles et réseau spécifiques.
    Voici un article intéressant sur le chargement adaptatif.
  • Lazy loading
    Le lazy loading consiste à ne charger que ce qui est visible sur la page et charger le reste au fur et à mesure que l’utilisateur navigue et a besoin de voir les éléments.
    Le plus souvent il s’agit des images car ce sont elles qui prennent le plus de poids.
    Il existe la propriété native HTML “loading” qui vous permet de mettre en place un lazy loading facilement avec la valeur “lazy”. Mais il existe aussi des librairie JS pour aller plus loin et gérer plus de cas.
  • Code splitting
    Vous pouvez aussi faire du “code splitting“ pour le code JS. C’est à dire charger une partie du code JS (d’un composant par exemple) uniquement quand vous l’utilisez. Cela réduira le poids du JS principal pour afficher la première page du site.
    Exemple d’outils ou librairies (React) :
    – React.lazy + Suspense : standard natif pour charger les composants à la demande.
    loadable-components : plus avancé, compatible SSR.
  • Mettre en cache certaines données API
    Il s’agit de garder en mémoire les réponses des appels API pour éviter de rappeler un même service dont les données ne changent pas souvent.
    Vous pouvez mettre en place une politique de cache côté client et garder un certains temps ces données en cache pour réduire le nombre des appels API.
    Exemple de librairies de cache côté client (React, Solid, Svelte and Vue) :
    TanStack Query : inclut une gestion fine du cache.

Les performances de chargement côté serveur

  • Algorithme de compression activé
    Activez toujours l’algorithme de compression des données (gzip ou autre) sur le serveur, cela réduit beaucoup le poids des données transférées.
    Exemple de librairies de compression :
    compression : middleware Express pour activer gzip facilement.
  • Mettre en cache les requêtes http autant que possible
    De même que côté client, il s’agit de garder en mémoire certaines réponses des appels API pour éviter de rappeler un même service dont les données ne changent pas souvent.
    Vous pouvez mettre en place une politique de mise en cache côté serveur en plus du cache client et garder un certain temps ces données en cache.

Les performances du code JS

Il s’agit ici de bonnes pratiques et d’optimisations pour réduire la vitesse d’exécution du code.

  • Utiliser le state du composant quand c’est possible au lieu du state global
    Si vous utilisez redux par exemple (ou un state global à l’application) il est préférable d’éviter de stocker des données qui n’ont pas d’intérêt à être partagées avec le reste de l’application. Utilisez plutôt un “state” dédié à votre composant. Le fait de modifier votre state global peut avoir un coût non négligeable car beaucoup de composants peuvent écouter ce state.
    Exemple de librairies de gestion d’état (React) :
    Zustand : très léger et efficace pour un state local ou global.
  • Mettre en cache les valeurs et les éléments dom qui ne changent pas pour éviter de re calculer la même chose
    Il s’agit tout simplement de garder dans une variable une valeur que vous utilisez beaucoup pour éviter de la re-calculer.
    Exemple : un élément dom, un résultat de calcul.
// 1 day en milliseconds
export const DAY_IN_MS = 24 * 60 * 60 * 1000;

// To get the element header with cache
const headerElementCached;
export const getHeaderElement = () => {
if(!headerElementCached) {
headerElementCached = document.getElementById("headerId")
}
return headerElementCached;
}
  • Mettre en cache le résultat de certaines requêtes pour éviter de faire un fetch
    Comme vu plus haut pour les performances de chargement, cacher certaines requêtes a aussi un bénéfice sur la vitesse d’exécution du code. Vous pouvez utiliser des librairies dédiées comme TanStack Query, ou gérer vous-même le cache.

Exemple avec “localStorage” (attention localStorage et sessionStorage ont une taille limite, 5Mb environ suivant le navigateur) :

const HOUR_IN_MS = 60 * 60 * 1000; // 1 hour in milliseconds precalculated

// Promise return list of videos from an api.
// With localstorage cache of 1 hour.
const getListVideos = async () => {
// try to get from localstorage
const cacheObjectString = localStorage.getItem("getListVideos");
if(cacheObjectString) {
let cacheObject;
try{
cacheObject = JSON.parse(cacheObjectString);
}catch(error){}

// return data if it's not expired
if(cacheObject && cacheObject.expire > Date.getTime()) {
return Promise.ressolve(cacheObject.data);
}
}

// if cache is not returned, we fetch the data
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Response status: ${response.status}`);
}

const json = await response.json();

// save result data in localstorage
const cacheObject = {
expire: Date.getTime() + HOUR_IN_MS, // cache valid 1 hour
data: json
}
localStorage.setItem("getListVideos", JSON.stringify(cacheObject));

return json;
}
  • Ré-utiliser des instances de composants
    Il s’agit de créer une seule instance d’un composant et la garder en mémoire pour la ré-utiliser quand on en a besoin.
    A faire surtout sur les composants qui ont pas mal de choses à initialiser et qui sont utilisés souvent. Exemple : un player.
    Il peut s’agir aussi d’une longue liste où il serait nécessaire d’afficher et instancier seulement ce qui est affiché à l’écran.
    Exemple de librairies utiles pour les listes ou composants lourds (React) :
    TanStack Virtual : rendu performant de grandes listes.

Quelques optimisations de code JS

  • Conversion de nombre flottants
    Au lieu d’utiliser math.floor, math.ceil et math.round pour arrondir, utiliser “~” qui est plus rapide sur la plupart des navigateurs (à tester au cas par cas) :
var testFloor = ~~15.8;
var testCeil = -~15.8;
console.log(testFloor); // 15
console.log(testCeil); // 16
  • Vous pouvez utiliserlengthpour vider un tableau et éviter de créer une nouvelle instance :
var array = [1, 2, 3, 4, 5, 6]; 
array.length = 0;
console.log(array.length); // 0
console.log(array); // []
  • Concaténer 2 tableaux
    Pour concaténer de grands tableaux, utiliser plutôt Array.push.apply(arr1, arr2).
    Le classique Array.concat()sur de grands tableaux consomme beaucoup de mémoire car il crée un nouveau tableau séparé.
var array1 = [1, 2, 3]; 
var array2 = [4, 5, 6];
console.log(Array.push.apply(array1, array2)); // [1,2,3,4,5,6];
  • Obtenir le dernier élément d’un tableau
    Afin d’éviter d’appeler Array.length , une astuce consiste à utiliser Array.slice :
var array = [1, 2, 3, 4, 5, 6]; 
console.log(array.slice(-1)); // [6]
console.log(array.slice(-2)); // [5,6]
console.log(array.slice(-3)); // [4,5,6]
  • Boucle sur un tableau
    Les boucles for sont plus rapides que les boucles avec foreach ou map :
instead of 

arr.forEach(function (item) {
someFn(item);
})

Use:

for (var i = 0, len = arr.length; i < len; i++) {
someFn(arr[i]);
}

Pour aller plus loin

Il est bien de connaître quelques bonnes pratiques mais le mieux, en matière de performances, est de faire des tests soi-même sur des cas précis.
Pour mesurer les performances du code JS, vous pouvez utiliser le débugueur du navigateur. La plupart des navigateurs inclut un outil de mesure de performance (Google Chrome Lighthouse par exemple).

Outils de test et de benchmark :

  • Jsperf.app : pour publier et tester vos snippets JS.
  • Jsbench.me : alternative moderne à jsperf, plus rapide à utiliser.
  • Lighthouse : outil complet de Google pour tester performance, accessibilité, SEO…
  • Bundlephobia : pour connaître le poids réel de vos dépendances npm.

Sources :

Suivez les actualités d’ekino sur notre site et sur LinkedIn.


Quelques bonnes pratiques pour la performance web was originally published in ekino-france on Medium, where people are continuing the conversation by highlighting and responding to this story.