Accélérer vos pages Web : quelques réflexes ⚡️ (partie 1)

Alexandre Cantin
Jul 7 · 11 min read

Cet article sera le premier d’une série de trois :

  1. Images et polices personnalisées (cet article)
  2. JavaScript et pré-chargement de ressources : https://link.medium.com/xxRg7Yc7j8
  3. Contenus animés et LightHouse (à venir)

Depuis sa naissance en 1989, le Web n’a cessé d’offrir plus de possibilités aux développeurs afin de créer la meilleure expérience possible pour nos utilisateurs : images, vidéos, interfaces dynamiques avec JavaScript, polices personnalisées etc.
Toutefois, cela n’est pas sans conséquence : le poids de nos pages a augmenté et il n’est pas rare qu’une page Web dépasse désormais un voire plusieurs mégaoctets.

D’ailleurs, selon [Httparchive.org](https://httparchive.org/), en dix ans, malgré l’augmentation constante des débits Internet, la vitesse de chargement des pages est restée… la même. De plus, Google considère désormais les performances comme un facteur influençant son algorithme de _ranking_.

Dans cet article, nous présenterons ainsi différentes méthodes pour alléger vos pages. Cette liste sera non exhaustive et nous nous concentrerons sur les principaux leviers parmi la centaine (voire les milliers!) envisageables.

Note : les éléments présentés ci-dessous visent les utilisateurs arrivant pour la première fois sur votre site. Par conséquent, les services workers ne seront pas abordés.
De même, je n’aborderai pas des techniques que je considère comme déjà acquises comme la minification du CSS et du JavaScript

🏞 1 - Les images

1.1 - Réduction du poids : PNG et JPG
Omniprésentes, les images sont les principaux responsables de l’alourdissement du Web. Leur bonne gestion doit donc être l’une de vos priorités et j’oserai même dire un réflexe indispensable en tant que développeur.

Pour ma part, je ne peux que vous conseillez d’utiliser https://tinypng.com/ qui fonctionne via un simple drag and drop.

Image for post

Nous allons maintenant tester son efficacité avec l’image de ce chat au format JPEG et pesant 357 Ko sans optimisation :

Image for post

Si nous l’optimisons, TinyPng nous indique que le poids du fichier a été réduit de 49%, de 357 Ko à 181 Ko :

Image for post

Un poids divisé de moitié pour un rendu équivalent :

Image for post

Sachant que ce gain se répète pour chacune image de votre page, on comprend vite l’intérêt crucial de bien optimiser l’ensemble de ses images.

1.2 - Quand choisir JPEG et PNG ?

Un autre point important est de choisir le bon format en fonction de vos besoins. En effet, ils répondent tous les deux à des exigences différentes et votre choix dépendra de plusieurs facteurs :

  • la taille de l’image : pour les “grandes” images (photos notamment), il faudra privilégier le format JPEG. Pour les “petites” images (émoticônes, icônes…), il faudra opter pour les PNG. Et si votre image est de taille “moyenne” ? Il faudra mener votre propre enquête pour déterminer le meilleur choix (en fonction du poids mais aussi des autres critères).
  • précision du dessin : le format JPEG, étant un format compressé, les contours perdent en netteté et cela n’est pas forcément souhaitable (graphiques, logo etc.). Il faudra opter pour le format PNG dans ce cas.
  • transparence : dans ce cas-là, le PNG est la seule solution 🙂.

1.3 - Réduction du poids : SVG

Les images SVG peuvent aussi être optimisées. Pour ma part, j’utilise SVGOMG qui fonctionne lui aussi via drag and drop.

Image for post

Dans l’exemple ci-dessus, on constate que le poids de notre SVG a réduit de 60%, passant de 1.5 Ko à 643 bytes et, même si le résultat reste moins spectaculaire comparé aux PNG et JPG, cela reste toutefois appréciable 🙂.

1.4 - Allez plus loin avec Webp

Créé par Google en 2010, le Webp est un format d’image qui se veut - en moyenne - 30% plus léger comparé au JPEG et 25% par rapport au PNG; tout en conservant gardant les effets de transparence pour ce dernier format.

Le Webp est donc un remplaçant de choix pour tous vos fichiers JPG et au niveau des PNG, Webp étant un format compressé, cela dépendra principalement de la précision du contour que vous souhaitez obtenir.

Image for post

Un des soucis majeur de ce format est son support dans les principaux navigateurs. En effet, bien que Chrome, Firefox et Edge (uniquement dans sa dernière version) acceptent ce format, Safari le supportera dans sa prochaine version :

Image for post

Cela ne doit pas pour autant vous décourager d’utiliser le format Webp et nous verrons plus tard comme gérer les navigateurs non compatibles.

Pour convertir vos images en Webp, je peux vous conseiller d’utiliser XnConvert qui fonctionne directement sur votre ordinateur (disponible pour Windows/Linux/MacOs). Comme solutions en ligne, https://ezgif.com/png-to-webp et https://ezgif.com/jpg-to-webp fonctionnent très bien 🙂.

La conversion se réalise en 3 étapes (nous partirons de l’image du chat optimisée précédemment) :

  1. Ajouter les images :
Image for post

2. Cliquer sur l’onglet “Sortie” puis :

  • Indiquer “{Filename}.png” ou “{Filename}.jpeg” dans le champ “Nom du fichier”. Le choix de ce formatage sera important pour la suite 😉
  • Choisir “WEBP-Webp” dans le bloc format
  • Décocher la case “Préserver les métadonnées” et cocher “Préserver l’extension”
Image for post

3. Pour finir, cliquer sur “Convertir” en bas à droite de la fenêtre :

Image for post

Nous obtenons ainsi une image avec le nom : [nom-image].jpeg.webp (ou [nom-image].png.webp). Dans notre exemple, le poids est passé de 181 Ko à 149 Ko.

1.4.1 - Cohabitation entre Webp et PNG/JPEG avec <figure>

Introduite en 2015, la balise <picture> permet de spécifier plusieurs règles afin que le navigateur puisse déterminer l'image la plus pertinente à télécharger. Plusieurs conditions sont possibles : densité de pixels, dimensions du navigateur... et le format d'image 🙂.

Pour utiliser <picture>, il suffit de lui associer plusieurs <source> :

<picture>
<source srcset="chat-mignon.webp" type="image/webp">
<source srcset="chat-mignon.jpeg" type="image/jpeg">
<img src="chat-mignon.jpeg" alt="" />
</picture>

Dans cet exemple, le navigateur optera pour le format Webp s’il le gère et JPEG dans le cas contraire. L’usage de <picture> est donc d'une grande simplicité 🙂.

Malheureusement, il nous reste le cas d’une image importée via CSS et malheureusement, il n’existe pas d’équivalents à <picture> pour le CSS.

La solution est alors d’utiliser JavaScript pour déterminer ce support et notamment via ce snippet (proposé par Jake Archibald) :

async function supportsWebp() {
if (!self.createImageBitmap) return false;

const webpData = 'data:image/webp;base64,UklGRh4AAABXRUJQVlA4TBEAAAAvAAAAAAfQ//73v/+BiOh/AAA=';
const blob = await fetch(webpData).then(r => r.blob());
return createImageBitmap(blob).then(() => true, () => false);
}

En fonction de la réponse, vous pourrez ajouter une classe webp ou no-webp à la balise <body> pour, ensuite, au niveau du CSS, utiliser cette classe pour appeler la bonne image :

.no-webp .cat-background {
background-image: url("chat-mignon.jpg");
}

.webp .cat-background {
background-image: url("chat-mignon.webp");
}

1.4.2 - Cohabitation entre Webp et PNG/JPEG avec nginx

Si vous ne souhaitez pas convertir l’ensemble de vos balises <img /> de votre projet ou d'utiliser une classe à la balise <body>, il est possible de délivrer le bon format d'image en fonction du support du navigateur et cela grâce à Nginx.

Pour obtenir ce comportement, la premier prérequis sera de faire cohabiter l’image JPEG/PNG à côté de son équivalent Webp. Nous obtenons ainsi :

images/
- chat.jpeg
- chat.jpeg.webp

Dans un second temps, nous utiliserons l’instruction map de Nginx pour créer une variable $webp_suffix. Cette dernière contiendra .webp ou sera vide en fonction du support de Webp par le navigateur. Ce support sera déterminé suivant le contenu du header HTTP Accept de la requête :

# Detect if browser accepts .webp images ?
map $http_accept $webp_suffix {
default"";
"~*webp" ".webp";
}

Pour finir, nous utiliserons la commande try_files de Nginx, permettant de tester la présence de plusieurs fichiers successivement et renvoyer le premier trouvé.

# Handle .webp

location ~* ^/assets/.+\.(jpe?g|png)$ {
root /home/deploy/build/;
try_files $uri$webp_suffix $uri =404;
}

Ainsi, nous testerons dans l’ordre :
1 — l’URL de la ressource concaténée avec la valeur $webp_suffix pouvant donc être <nom-image>.jpeg.webp ou <nom-image>.jpeg
2 - l'URL demandée en solution de secours (en cas d'absence du fichier Webp notamment)
3 - Erreur 404

Nous obtenons un système faisant cohabiter à la fois les images au format JPEG/PNG et Webp, basée sur la prise en charge du format par le navigateur de l’utilisateur.

D’ailleurs, on constate sur la console de Google Chrome que le fichier reçu est bien une image Webp :

Image for post

1.5 - Lazy-loading

Les images les plus légères restent celles qu’on ne télécharge pas. Pour cela, le lazy-loading est la solution toute nommée et consiste à télécharger une ressource seulement si cela s’avère nécessaire. Dans le cas d’une image, l’élément déclenchant son téléchargement sera ainsi son apparition — ou sa proche apparition — dans la zone d’affichage de la page Web.

1.5.1 - Avec Lazysizes

Lazysizes est une librairie JavaScript nous facilitant la mise en place du lazy loading d’images. D’une taille compressée de 3.2Ko, son utilisation se veut assez simple en renommant notre attribut src en data-src:

<img data-src="static/logo.jpg" class="lazyload" alt="" />

De plus, Lazysize nous permet aussi d’adapter les images en fonction de l’appareil de l’utilisateur (mobile, tablette ou PC).

<img
alt=""
class="lazyload"
data-srcset="small.jpg 500w, medium.jpg 640w, big.jpg 1024w"
data-src="medium.jpg" />

Simple mais terriblement efficace 🙂.

1.5.2 - Avec l’attribut loading de img

La balise img accueille depuis peu un nouvel attribut loading qui, couplé avec la valeur lazy, permet d'implémenter nativement un système de lazy loading :

<img src="chat-mignon.png" loading="lazy" alt="" />
Image for post

Sa prise en compte est donc loin d’être garantie… Toutefois, vous ne perdez rien à implémenter ce nouvel attribut qui sera tout simplement ignoré par les navigateurs non compatibles 🙂 (qui téléchargeront donc directement l’image).

🚨 2 - Les polices personnalisées

Relativement récent dans l’histoire du Web, l’ajout des polices personnalisées apporte une touche de personnalisation supplémentaire au design de nos pages. Et même s’elles sont minimes, des optimisations restent possibles.

2.1 - Fonctionnement des polices personnalisées

Avant de nous attarder sur les améliorations de performances possibles, il est nécessaire de présenter le fonctionnement des polices personnalisées.

Tout d’abord, l’inclusion d’une police peut se réaliser de plusieurs manières mais les deux méthodes les plus courantes sont :

1- L’import direct de la police depuis notre serveur

@font-face {
font-family: "Open Sans";
src: url("/fonts/OpenSans-Regular-webfont.woff2") format("woff2"),
url("/fonts/OpenSans-Regular-webfont.woff") format("woff");
}

2- Via une ressource externe (ici, Google Fonts) :

<link href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@300" rel="stylesheet">

Dans tous les cas, l’utilisation dans nos styles CSS est identique :

body {
font-family: "Open Sans", serif;
}

Bien entendu, la police n’est pas directement présente sur le navigateur et doit donc être téléchargée. Mais que se passe-t-il pendant ce temps au niveau de l’affichage ? Figurez-vous que le navigateur n’affiche tout simplement…rien ! Plus exactement, il compense l’absence de la police en la remplaçant par une police invisible dont la taille se veut proche du résultat final (via font-size et font-weight principalement).

Image for post

Par défaut, cette situation va durer 3 secondes (sur Firefox et Chrome tout du moins) avant que le navigateur bascule sur la seconde police (serif dans notre exemple). Trois secondes pendant lesquelles l’utilisateur ne pourra lire le contenu de votre site…

Bien sûr, dès que la police sera téléchargée, l’affichage de votre page sera mis à jour (que ce soit avant ou après les 3 secondes de délai).

2.2 - Utilisation de display: swap

La règle CSS font-display permet de modifier la politique d'import des polices personnalisées, sa valeur par défaut est auto et correspond au scénario décrit précédemment.

En remplaçant auto par swap, le délai de basculement vers la police de remplacement passe alors de 3 secondes à 100 ms ! Un gain de temps appréciable et réduisant ainsi le Time to first meaningful paint, métrique définie par Google correspondant au temps d'apparition du premier contenu pertinent.

Son utilisation est extrêmement simple.

Dans notre premier cas d’import, nous ajoutons simplement font-display: swap; dans le bloc @font-face :

@font-face {
font-family: "Open Sans";
src: url("/fonts/OpenSans-Regular-webfont.woff2") format("woff2"),
url("/fonts/OpenSans-Regular-webfont.woff") format("woff");
font-display: swap;
}

Pour Google Fonts, depuis mai 2019, il suffit d’ajouter ‘&display=swap’ dans l’URL d’import pour obtenir, par exemple :

<link href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@300&display=swap" rel="stylesheet">

Si vous souhaitez gérer plus finement votre police d’import de polices, font-display autorise d'autres valeurs :

  • font-display: block
    Cette option est semblable au fonctionnement par défaut : 3 secondes de police invisible avant de basculer sur la police de remplacement. Son seul intérêt est de s’assurer que nous ayons bien ces 3 secondes de délai, étant donné que tous les navigateurs peuvent avoir une politique auto différente comparée à celle de Firefox et Chrome.
  • font-display: fallback
    L’option fallback réduit aussi le délai de basculement vers la police de remplacement à 100 ms mais limite aussi la durée de récupération de la police personnalisée à 3 secondes (là où swap n'impose pas de durée de temps). Elle permet donc d'indiquer au navigateur d'arrêter la récupération de la police si son téléchargement s'avère trop long.
  • font-display: optional
    Plus radicale que fallback, elle réduit le délai de basculement à 100 ms mais arrête la récupération de la police par la même occasion. Traduction pour le navigateur: "Si tu as la police sous la main (donc en cache) tu l'utilises, sinon inutile de la récupérer".

Fin de la première partie

Nous voici arrivés à la fin de cette première partie consacrée aux images et aux polices personnalisées.
Dans le prochain épisode, nous aborderons les optimisations liées JavaScript et le pré-chargement de ressources.

Restez connecté 🙂.

Restez connecté 🙂.

🔰 Aller plus loin

Vidéos et articles

Voici un ensemble de ressources pour explorer l’univers de l’optimisation des chargements :

Sur Twitter

CodeShake

Learnings and insights from SFEIR community.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store