Accélérer vos pages Web : quelques réflexes ⚡️ (partie 1)
Tags: frontend, optimization, coding
Published: 2020-06-08 19:46:16

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 (à venir)
  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
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.

### 1.1 - Réduction du poids : PNG et JPG
Peut-être l'ignorez-vous (ou n'y pensez simplement pas) mais il est possible d'optimiser les images pour en réduire -drastiquement- le poids.
Pour ma part, je ne peux que vous conseillez d'utiliser [https://tinypng.com/](https://tinypng.com/) qui fonctionne via un simple _drag and drop_.

![Image de la page d'accueil de TinyPNG](https://dev-to-uploads.s3.amazonaws.com/i/emepr5gelf0vfwn448fj.png)

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

![Image d'un chat mignon de 357 Ko](https://dev-to-uploads.s3.amazonaws.com/i/3xkgccus5936km1z7l9y.jpg)

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

![Fenêtre de rendu de TinyPng après conversion](https://dev-to-uploads.s3.amazonaws.com/i/cexa0xyqifylbuq9pur8.png)

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

![Le même chat... en plus léger](https://dev-to-uploads.s3.amazonaws.com/i/5f3l067ijz8rqc8hs34w.jpg)

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](https://jakearchibald.github.io/svgomg/) qui fonctionne lui aussi via _drag and drop_.

![Interface de SVGOMG](https://dev-to-uploads.s3.amazonaws.com/i/vjy0wpfrcji1hhxjrjxb.png)

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.

![Comparaison Webp et JPEG : un poids de fichier inférieur de 35%](https://dev-to-uploads.s3.amazonaws.com/i/3ohae6tfbugruibyaa6t.png)

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 : 

![](https://dev-to-uploads.s3.amazonaws.com/i/3divqbvqv05scjqk82by.png)

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](https://www.xnview.com/fr/xnconvert/) qui fonctionne directement sur votre ordinateur (disponible pour Windows/Linux/MacOs). Comme solutions en ligne, [https://ezgif.com/png-to-webp](https://ezgif.com/png-to-webp) et [https://ezgif.com/jpg-to-webp](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 :

![Etape 1 : Liste des images à convertir sur XnConvert](https://dev-to-uploads.s3.amazonaws.com/i/08cvhlizcx9b1p27210x.png)

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"

![Etape 2 : choix du format, nom du fichier... sur XnConvert](https://dev-to-uploads.s3.amazonaws.com/i/etqizxiclexbci9ns767.png)

3- Pour finir, cliquer sur "Convertir" en bas à droite de la fenêtre :

![Etape 3 : fenêtre de confirmation des conversions sur XnConvert](https://dev-to-uploads.s3.amazonaws.com/i/dchkm40ngo098wh1gjzm.png)

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](https://twitter.com/jaffathecake/status/893109104205496320)) :

```
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 :

```nginx
    # 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é.

```nginx
    # 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 :

![Console Chrome avec format de retour en Webp](https://dev-to-uploads.s3.amazonaws.com/i/oxmf2uwsadogowh7up55.png)


### 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](https://github.com/aFarkas/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`:

```html
    <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).

```html
    <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_ :

```html
<img src="chat-mignon.png" loading="lazy" alt="" />
```

![Un support pas top...](https://dev-to-uploads.s3.amazonaws.com/i/jkhxn3vhaatp88vkfbjh.png)


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
```css
@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) :
```css
<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 :
```css
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).

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/pf7791f86mz914nbh4wc.png)

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` :
```css
  @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 :

```css
<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é 🙂.

---

## 🔰 Aller plus loin

### Vidéos et articles
Voici un ensemble de ressources pour explorer l'univers de l'optimisation des chargements :

- Fast load times : https://web.dev/fast/

- Lazy-loading d'images et _iframe_ : https://web.dev/native-lazy-loading/

- Web performance made easy (Google I/O '18) :
 https://www.youtube.com/watch?v=Mv-l3-tJgGk

- Speed at Scale: Web Performance Tips and Tricks from the Trenches (Google I/O ’19) : https://www.youtube.com/watch?v=YJGCZCaIZkQ

- Polices personnalisées : 
https://developers.google.com/web/updates/2016/02/font-display

### Sur Twitter
- Houssein Djirdeh : https://twitter.com/hdjirdeh
- Addy Osmani : https://twitter.com/addyosmani
- Katie Hempenius : https://twitter.com/katiehempenius
- Paul Irish : https://twitter.com/paul_irish
- Lighthouse : https://twitter.com/____lighthouse