L’autre jour, je m’amusais à tester mon site Van11y sur divers outils, et notamment celui-ci : Sonarwhal.
Ce dernier propose une piste d’amélioration que je n’avais pas essayée jusqu’à présent, à savoir implémenter Cache-Control: immutable
. J’avais entendu parler de cette possibilité via ma veille habituelle, mais je n’avais jamais essayé en pratique.
Cache-control: immutable
Cette possibilité a été motivée principalement par de gros sites comme Facebook. Ces derniers ont constaté que bon nombre de leurs utilisateurs utilisaient énormément la touche F5 (allez savoir pourquoi !). Les éléments dits statiques sont bien mis en cache, mais toutefois le navigateur doit deviner quels éléments ont été modifiés, il réinterroge donc le serveur qui lui renvoie un code 304, qui lui signifie « Non modifié ».
En pratique, sur de tous petits fichiers, le coût de cette revalidation est plus élevé que de re-télécharger le fichier en question. Ajoutons à cela qu’un site comme Facebook a beaucoup de fichiers statiques (images, etc.) et d’énormes besoins en temps serveur et en bande-passante. Et comme dirait La Palice :
La requête la plus rapide est celle que l’on ne fait jamais.
Bref, l’intérêt de Cache-Control: immutable
? Grosso modo, c’est d’éviter cette étape de validation, en disant au navigateur que l’élément en question ne changera jamais, il est dit « immutable ».
Ce « petit changement » implique certaines précautions.
Avoir une stratégie de cache-busting
J’en avais une sur les CSS et sur le JavaScript (qui sont faits automatiquement), mais j’étais quelque peu fainéant sur d’autres éléments. Qu’à cela ne tienne, c’était l’occasion de s’amuser à le faire comme il faut, sur un site avec relativement peu de contenus.
Autant le dire de suite : dans ce cas, je l’ai fait de manière très artisanale. La plupart des CMS et bon nombre d’autres systèmes le font pour vous automatiquement.
En pratique, sans cette stratégie, si vous envoyez le fichier /images/logo.svg
, et si vous deviez mettre à jour ce fichier sur votre serveur, il faudra le renommer. Vous voyez l’écueil ? Changer à la main le nom d’un fichier sur un nombre conséquent de pages n’est absolument pas gérable.
Il existe toutes sortes de techniques de cache-busting, mais la plus passe-partout consiste à utiliser de l’URL-rewriting (la réécriture d’adresse en bon français). En clair, on va demander au serveur de réécrire l’adresse /images/logo_<ici une valeur qu’on fera changer>.svg
en /images/logo.svg
. Cela peut se faire via un htaccess
:
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.+)_(\d+)\.(css|js)$ $1.$3 [L]
En clair, si l’URL demandée n’est ni un fichier ni un répertoire, et avec les extensions ci-dessus, alors fichier_xxxxxxxx.extension
va être réécrit ainsi sur le serveur fichier.extension
. Par exemple, css/styles_mini_1516114378.css
sera réécrit en css/styles_mini.css
(et ce fichier est bien sur mon serveur).
Par contre, c’est bien l’URL css/styles_mini_1516114378.css
qui va être mise en cache. Une mise à jour sur la CSS ? Le cache-buster va être mis à jour, par exemple en css/styles_mini_1516114666.css
.
En pratique pour le cache-busting
Grosso modo, j’ai repéré tous les types de fichiers qui devaient en bénéficier, et j’ai mis cela dans mon htaccess
principal :
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.+)_(\d+)\.(css|js|webmanifest|ico|png|jpg|svg|js|eot|ttf|woff|woff2)$ $1.$3 [L]
Ensuite, il a fallu ajouter le cache-buster partout où il est nécessaire. Sur les img
de contenu, j’ai utilisé une fonction en PHP qui renvoie le timestamp Unix de dernière modification du fichier (filemtime()) et qui insère le cache-buster dans le nom du fichier.
return '_'.filemtime($prefix. $chemin_img);
Dans la CSS, pour les rares images et autres éléments utilisés… fainéantise absolue, je l’ai fait à la main. Si si. Il existe des outils qui le font pour vous. Là, les besoins étaient si maigres… que j’ai eu la flemme.
Pour les fichiers CSS, là j’avais déjà des fonctions qui le faisaient automatiquement, en plus de faire d’autres choses (minification, calcul d’intégrité, etc.). Idem pour le JavaScript.
Comme les démos du site sont reprises depuis les dépôts Github de chaque projet (pour m’éviter de me fatiguer quand je les mets à jour), j’ai modifié les fonctions qui récupèrent les fichiers pour ajouter les cache-busters aux bons endroits (CSS et JavaScript). Hop, tout est automatisé, point d’effort.
En pratique pour déployer l’en-tête proprement dite
Une fois la stratégie de cache-busting correctement déployée, il ne reste plus grand chose à faire, j’ai mis ceci dans mon htaccess
habituel :
<FilesMatch "\.(css|js|webmanifest|ico|png|jpg|svg|js|eot|ttf|woff|woff2)$">
<IfModule mod_headers.c>
Header set Cache-Control "public, max-age=31536000, immutable"
...
</IfModule>
</FilesMatch>
Et c’est terminé.
Un point de détail amusant : Sonarwhal va – à juste titre – vous gronder si vous déployez Cache-Control: immutable
sans stratégie de cache-busting. L’outil ne reconnaissait pas celle que j’avais utilisée, je le signale aux développeurs, qui ont été super réactifs et qui ont mis l’outil à jour. Un bravo à eux !
Un autre point de détail : Cache-Control: immutable
ne sera honoré sous Firefox que si le contenu est servi en HTTPS. Cela semble assez logique : il faut être sûr que le contenu n’ait pas été modifié entre le serveur et le navigateur.
En conclusion
Disons-le clairement, l’impact sur les performances sur un site ultra-léger et rapide comme Van11y est quasi-nul. C’est d’autant plus beau que c’est totalement dispensable. :)
C’est un petit exercice de style auquel je me suis livré, plus pour m’amuser qu’autre chose. C’est toujours l’occasion de mettre des bonnes pratiques en place, sur des projets sympathiques et peu chronophages. Ces bonnes pratiques sont utiles même si vous ne déployez pas Cache-Control: immutable
.
Pour en savoir plus :