Introduction
Si vous avez déjà utilisé Google Page Speed Insights, alors vous avez sans doute rencontré le message « Diffusez des images aux formats nouvelle génération ». L’article d’aujourd’hui se concentre justement sur les images au format next-gen WEBP, et plus précisément comment les servir automatiquement aux navigateurs qui les supportent à la place des images standards JPEG et PNG. Le but est d’éviter d’avoir à modifier le code de toutes les pages du site pour pouvoir servir ces images quand c’est possible.
Concrètement, si un navigateur compatible demande une image https://example.com/image.png, il se verra servir en toute transparence (sans redirection) une image https://example.com/image.webp.
Note : le code a été testé sur un environnement Apache 2.4 chez l’hébergeur o2switch. N’oubliez pas de toujours faire une sauvegarde avant de modifier les fichiers de votre site !
Pré-requis
Pour pouvoir utiliser le code de cet article vous devez déjà disposer des images de votre site au format WEBP, mises en ligne dans le même dossier que le format normal, avec le même nom, mis à part l’extension. Par exemple, si vous avez une image appelée image.jpg dans un dossier /site.com/images, vous devrez avoir une image appelée image.webp dans ce même dossier. Bien que le code que nous allons écrire gère le cas du simple rajout de .webp à l’extension déjà existante (exemple : image.jpg.webp), garder l’ancienne extension n’est pas recommandé.
Voici une capture d’exemple basée sur le site Code Colibri.
Si vous n’avez pas l’image au format WEBP, il va falloir la générer, par exemple avec l’outil en ligne de commande cwebp (voir la page de téléchargement et la documentation de la commande cwebp), ou depuis un site internet. Afin de limiter la longueur de cet article, j’en dirai plus dans une prochaine publication.
Optimisation : service automatique des images WEBP
Ajouter le support des fichiers .webp à Apache
Si votre Apache est un peu âgé alors il peut avoir besoin d’un rafraichissement… Les hébergeurs web ont parfois des versions d’Apache qui ne connaissent pas le format WEBP par défaut. Cela se traduit par un entête Content-Type
avec un type MIME inattendu (en général application/octet-stream
) qui déclenche le téléchargement du fichier au lieu de son affichage en tant qu’image. Pour tester c’est facile, tentez d’accéder avec votre navigateur compatible à une image WEBP, si un téléchargement démarre ou si l’entête Content-Type: image/webp
n’est pas présent alors il faut indiquer à Apache ce qu’est une image WEBP.
Tout ce que nous avons à faire est de faire correspondre le bon type MIME (image/webp) à l’extension de fichier .webp, cela correspond au code ci-dessous (à insérer dans votre fichier htaccess) :
A noter que si votre serveur gère déjà le format WEBP ce code est optionnel, mais ne causera pas de problème si vous l’insérez tout de même.
Réécrire les requêtes vers les images WEBP si supporté par le navigateur
Retourner de façon transparente le contenu WEBP au lieu du contenu classique se fait par le biais d’une réécriture interne – seul le serveur web en a connaissance, contrairement à une redirection où le navigateur est également au courant. Nous devons trouver le fichier WEBP de remplacement s’il existe, et modifier la requête en interne pour qu’elle pointe dessus si le navigateur déclare supporter le format WEBP.
Tout d’abord, voici le code qui fait cela. Je le décrirai juste après :
<IfModule mod_rewrite.c>
RewriteEngine On
# Trouver le fichier webp
# 1) fichier.jpg -> fichier.jpg.webp
RewriteCond %{REQUEST_URI} ^(.+)\.(?:jpe?g|png)$
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI}.webp -f
RewriteRule ^(.+)$ "-" [NC,E=replacewebp:$1.webp]
# 2) fichier.jpg -> fichier.webp
RewriteCond %{REQUEST_URI} ^(.+)\.(?:jpe?g|png)$
RewriteCond %{DOCUMENT_ROOT}/%1.webp -f
RewriteRule (.+)\.(?:jpe?g|png)$ "-" [NC,E=replacewebp:$1.webp]
# Réécriture vers le fichier webp s'il existe et est supporté
RewriteCond %{ENV:replacewebp} !^$
RewriteCond %{HTTP_ACCEPT} image/webp
RewriteCond %{QUERY_STRING} !nooptim
RewriteRule ^(.+)$ %{ENV:replacewebp} [NC,T=image/webp,L]
</IfModule>
Dans un premier temps on cherche un fichier WEBP qui existe, soit <la requete complete>.webp (exemple : /dossier/image.jpg.webp), soit <la requete sans extension>.webp (exemple : /dossier/image.webp). Si un fichier est trouvé, son chemin est stocké dans une variable d’environnement créée pour l’occasion, que nous appelons replacewebp
. L’utilité de cette variable est double :
- Si elle existe (non-vide) cela signifie qu’il existe un équivalent WEBP à l’image actuellement demandée
- Elle contient le chemin de l’équivalent WEBP, que nous pouvons utiliser si le navigateur déclare le supporter
Dans un deuxième temps on réécrit la requête en interne vers l’équivalent WEBP (la valeur de replacewebp
) si les conditions suivantes sont réunies :
- L’équivalent WEBP existe bel et bien (la variable
replacewebp
n’est pas vide) - Le navigateur a envoyé un entête
Accept
contenant image/webp, indiquant qu’il est compatible - La requête d’image ne contient pas nooptim dans sa query string (exemple : image.png?nooptim=1)
On garde donc la possibilité de servir le format standard même si le navigateur supporte le format WEBP… C’est plus prudent !
Ajouter le header Vary: Accept pour les images avec alternative WEBP
Maintenant que pour une même URL le serveur peut retourner deux contenus différents, il est nécessaire d’informer le navigateur du visiteur et les éventuels intermédiaires de ce qui cause la variation du contenu servi. Cela se fait avec l’entête Vary
, dont la syntaxe est Vary: Nom-du-header-variable
. Ici ca sera Vary: Accept
(car le contenu retourné varie selon la valeur de l’entête Accept
envoyé par le navigateur).
Pourquoi est-ce important ? Vous savez sans doute que les navigateurs peuvent conserver un cache des ressources utilisées sur les sites visités. Lors d’une première requête le contenu utilisé est celui renvoyé par le serveur (il est alors mis en cache par le navigateur), puis lors des suivantes la version en cache est utilisée pour gagner du temps. Que se passe t’il si la première fois on était compatible WEBP mais que la seconde fois ce n’est pas le cas ? Cela dépend :
- Sans entête
Vary
le navigateur va considérer que la ressource ne varie pas malgré l’absence de compatibilité WEBP la seconde fois, et va utiliser l’image WEBP en cache, provoquant une erreur d’affichage - Avec entête
Vary
le navigateur va considérer que la ressource varie peut-être (l’entêteAccept
étant différente), et va refaire une demande au serveur
S’il est improbable d’avoir un navigateur compatible WEBP qui subitement ne le soit plus, il est fréquent que le problème se pose avec les proxy-caches des entreprises. Les navigateurs compatibles feront récupérer le format WEBP au proxy, et sans l’entête Vary
, ce dernier risque de reservir ce même contenu WEBP aux navigateurs incompatibles !
Le code suivant effectue cette opération de modification d’entête :
<IfModule mod_headers.c>
# Ajouter le header Vary: Accept pour les images concernées
# 1) Si une réécriture a eu lieu
Header merge Vary Accept env=REDIRECT_replacewebp
# 2) Si une réécriture était possible mais n'a pas eu lieu
Header merge Vary Accept env=replacewebp
</IfModule>
Concrètement, on modifie l’entête si la variable d’environnement replacewebp
contenant le chemin du fichier WEBP existe et n’est pas vide (= si un fichier .webp a été trouvé). Un point de détail intéressant est le préfixe REDIRECT_ ajouté au nom de la variable d’environnement. Les variables d’environnement se retrouvent préfixées ainsi après une réécriture.
Le code complet
Voici le code complet à mettre en place dans le fichier htaccess de votre site. Pensez à faire une sauvegarde avant, ça peut servir en cas de version Apache incompatible.
# BEGIN WebP support
<IfModule mod_mime.c>
# Mapping extensions et types mime
AddType image/webp .webp
</IfModule>
<IfModule mod_rewrite.c>
RewriteEngine On
# Trouver le fichier webp
# 1) fichier.jpg -> fichier.jpg.webp
RewriteCond %{REQUEST_URI} ^(.+)\.(?:jpe?g|png)$
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI}.webp -f
RewriteRule ^(.+)$ "-" [NC,E=replacewebp:$1.webp]
# 2) fichier.jpg -> fichier.webp
RewriteCond %{REQUEST_URI} ^(.+)\.(?:jpe?g|png)$
RewriteCond %{DOCUMENT_ROOT}/%1.webp -f
RewriteRule (.+)\.(?:jpe?g|png)$ "-" [NC,E=replacewebp:$1.webp]
# Réécriture vers le fichier webp s'il existe et est supporté
RewriteCond %{ENV:replacewebp} !^$
RewriteCond %{HTTP_ACCEPT} image/webp
RewriteCond %{QUERY_STRING} !nooptim
RewriteRule ^(.+)$ %{ENV:replacewebp} [NC,T=image/webp,L]
</IfModule>
<IfModule mod_headers.c>
# Ajouter le header Vary: Accept pour les images concernées
# 1) Si une réécriture a eu lieu
Header merge Vary Accept env=REDIRECT_replacewebp
# 2) Si une réécriture était possible mais n'a pas eu lieu
Header merge Vary Accept env=replacewebp
</IfModule>
# END WebP support
Conclusion
Voila, nous servons automatiquement des images WEBP aux navigateurs compatibles si elles existent. Ce bout de code devrait sans doute rejoindre votre boite à outils, aussi bien pour vos futurs sites que vos travaux d’optimisation s’il y a lieu.
Bonne journée à vous !
Pour aller plus loin : le format AVIF
Les navigateurs récents supportent un autre format d’image – avec un meilleur taux de compression selon l’image – appelé le format AVIF, pour AV1 Image Format. A l’heure actuelle son support est encore peu répandu, mais vu les acteurs du web qu’il y a derrière (entre autres l’ensemble des GAFAM et Netflix), il devrait se développer assez rapidement.
Code Colibri déploie d’ores et déjà ce format sur ses sites et sur ceux de ses clients en plus du format WEBP, avec des configurations de serveur Apache servant au navigateur du visiteur le format qu’il supporte (original / WEBP / AVIF) en sélectionnant automatiquement la version la plus légère possible.
Ca vous intéresse ? Contactez-moi.
Références
CanIUse – Images WebP
Apache HTTPD – Documentation mod_headers
Apache HTTPD – Documentation mod_rewrite
Apache HTTPD – Documentation mod_mime
Google Developers – Conversion CWEBP