···30303131> J'ai clairement une marge d'amélioration sur l'accessibilité et les performances.
32323333+## Minification
3434+3535+Une première étape consiste à minifier les ressources statiques, HTML, CSS et JS.
3636+3737+Cette étape est très simple à mettre en place, car elle est déjà supportée par Hugo.
3838+Il suffit lors du build d'ajouter le flag `--minify` pour demander à Hugo de minifier toutes les ressources.
3939+4040+Ma commande de build est la suivante dans mon `mise.toml` :
4141+4242+```toml
4343+[tasks.build]
4444+description = "Build le site avec Hugo"
4545+run = "hugo --gc --minify --destination public"
4646+```
4747+4848+Ce qui produit des fichiers HTML minifiés de ce type :
4949+5050+```html
5151+<!doctype html><html xmlns=http://www.w3.org/1999/xhtml xml:lang=fr-FR lang=fr-FR><head><script defer language=javascript type=text/javascript src=/js/bundle.min.39a1898ad60dcb3b845d8dc359b7c996c10aa0da902f0d461da32348b1bc5f02.js></script><script defer data-domain=codeka.io src=https://plausible.io/js/script.js></script><script type=text/javascript src=https://app.affilizz.com/affilizz.js async></script><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><link rel=icon href=/favicon.png><meta property="og:image" content="/pp_ekite_itvw.png"><meta name=twitter:image content="/pp_ekite_itvw.png"><meta name=twitter:card content="summary_large_image"><meta property="og:image:width" content="639"><meta property="og:image:height" content="708"><meta property="og:image:type" content="image/png"><title itemprop=name>Julien Wittouck</title><meta property="og:title" content="Julien Wittouck"><meta name=twitter:title content="Julien Wittouck"><meta itemprop=name content="Julien Wittouck"><meta name=application-name content="Julien Wittouck"><meta property="og:site_name" content="Julien Wittouck">
5252+```
5353+5454+Hop, on peut passer rapidement à autre chose 🚶
5555+3356## Conversion des images en webp et redimensionnement
34573558Une des actions que j'ai mis en place il y a un moment, est l'utilisation du format _webp_ pour compresser les illustrations que j'utilise dans mes articles.
···38613962Ces photos sont souvent lourdes (plusieurs mégaoctets) et en haute résolution, et la première action simple consiste à redimensionner ces photo et les recompresser au format _webp_.
40634141-Hugo supporte la recompression des images dans différents formats à la volée, mais pas leur redimensionnement automatique.
6464+Hugo supporte la recompression des images dans différents formats à la volée, mais pas leur redimensionnement automatique, il faut implémenter soi-même la mécanique.
4265Pour pouvoir redimensionner les images à la volée, la meilleure solution semble d'utiliser un hook "img" Hugo, qui permet de surcharger la traduction du markdown et d'y mettre le code qu'on souhaite.
43664467Le hook utilisé par défaut est le suivant :
···6487{{- end -}}
6588{{- /* chomp trailing newline */ -}}
6689```
9090+9191+Je force l'utilisation de `lossless` avec la qualité maximale `q100` pour éviter une perte de données qui rendrait les illustations peu lisible, ce qui serait surtout problématique pour les schémas.
67926893## Pré-compression des ressources statiques
69949595+Les images étant maintenant compressées au build par Hugo, je peux m'atteler à la compression des ressources déjà minifiées (HTML, CSS et JS donc).
9696+9797+Avant de passer à la pré-compression en elle-même, il faut regarder comment les ressources seront servies.
9898+7099Mon site est hébergé chez Clever Cloud, dans une instance de type _static_.
71100J'avais écrit un article à ce sujet l'année dernière : [Déployer des applications statiques sur Clever Cloud](/2025/06//2025-06-05-static-apps-clever).
72101···87116encode
88117```
891189090-Lors de l'exécution d'une requête, Caddy va servir les fichiers statiques, et potentiellement compresser les réponses HTTP en alimentant le headers `Content-Encoding`.
119119+Lors de l'exécution d'une requête, Caddy va servir les fichiers statiques, et potentiellement compresser les réponses HTTP en alimentant le headers `Content-Encoding`. Les formats utilisés par défaut par Caddy sont `zstd` et `gzip`, et seules les ressources pertinentes sont compressées (les formats déjà compressés comme `jpg` ne sont pas re-compressés).
9112092121Cette compression permet d'économiser de la bande passante et accélère le temps de chargement des pages.
9312294123Cependant, la compression se fait en utilisant un peu de CPU à la volée.
95124Il est alors intéressant de pré-compresser les ressources statiques à la phase de build pour économiser un peu de CPU.
961259797-Un directive Caddy permet de servir des fichiers statiques pré-compressés : `precompressed`.
126126+Une directive Caddy permet de servir des fichiers statiques pré-compressés : `precompressed`.
98127Caddy va alors rechercher des variantes compressées des fichiers, sous la forme de fichiers sidecar.
9999-À côté de chaque fichier statique, il faut donc générer les variantes compressées et les nommer en utilisant les extensions `.br` et `.gz` par exemple.
128128+À côté de chaque fichier statique, il faut donc générer les variantes compressées et les nommer en utilisant les extensions `.gz`, `.br` et `.zst` par exemple.
100129101101-Hugo ne permet pas de générer ces variantes compressées, donc je dois utiliser un petit script qui s'exécutera en fin de la phase de build.
130130+Hugo ne permet pas de générer ces variantes compressées de lui-même, donc je dois utiliser un petit script qui s'exécutera en fin de la phase de build.
102131103103-J'ai donc créé ce script dans mon fichier `mise.toml`:
132132+J'ai donc créé ce script dans mon fichier `mise.toml` :
104133105134```toml
106135[tasks.build]
···114143[tasks.precompress]
115144description = "Precompress static resources"
116145run = '''
117117-COMPRESSREGEX=".*(html|css|js|xml|ico|svg|md|png|webp|pdf)$"
118118-find public/ -type f -regextype egrep -regex $COMPRESSREGEX | xargs zstd -15 -f
119119-find public/ -type f -regextype egrep -regex $COMPRESSREGEX | xargs zstd --format=gzip -15 -f
146146+COMPRESSREGEX=".*(html|css|js|xml|ico|svg|md|pdf|woff2)$"
147147+find public/ -type f -regextype egrep -regex $COMPRESSREGEX | xargs zstd --keep --force -19
148148+find public/ -type f -regextype egrep -regex $COMPRESSREGEX | xargs gzip --keep --force --best
120149'''
121150```
151151+152152+J'ai implémenté la compression avec `gzip` en utilisant le plus haut niveau de compression possible (`--best`), et avec `zstd` avec la plus forte compression également (`-19`). Le niveau de compression a surtout un impact à la compression, mais peu à la décompression, donc autant maximiser les différents niveaux.
153153+J'ai fait l'impasse sur le format `br` parce qu'il nécessite d'installer un binaire supplémentaire sur mes instances Clever Cloud, et que `gz` et `zst` sont déjà bien suffisants : `zst` sera supporté par les navigateurs modernes dans les versions les plus récentes, `gzip` fera office de format par défaut raisonnable.
122154123155Par défaut, Clever Cloud exécute une tâche `mise run build` si elle existe, donc l'ajouter dans mon fichier permet de pouvoir préciser mes options de build.
124156···145177 Cleaned │ 0 │ 0
146178147179Total in 272 ms
148148-[precompress] $ COMPRESSREGEX=".*(html|css|js|xml|ico|svg|md|png|webp|pdf)$"
149149-593 files compressed : 90.10% ( 165 MiB => 149 MiB) B ==> 98%%
150150-593 files compressed : 93.31% ( 165 MiB => 154 MiB)
180180+[precompress] $ COMPRESSREGEX=".*(html|css|js|xml|ico|svg|md|pdf|woff2)$"
181181+245 files compressed : 80.99% ( 83.3 MiB => 67.4 MiB) B ==> 98%^T
151182Finished in 7.77s
152183```
153184···155186156187```bash
157188$ ls public/
158158-2020 ai-manifesto icons logo_blue.png.zst pp_ekite_itvw_hu_41404e93ad715bdf.webp.gz
159159-2021 books images logo_transparent_background.png pp_ekite_itvw_hu_41404e93ad715bdf.webp.zst
160160-2022 credentials index.html logo_transparent_background.png.gz projects
161161-2023 css index.html.gz logo_transparent_background.png.zst robots.txt
162162-2024 ekite index.html.zst now series
163163-2025 en index.xml page sitemap.xml
164164-2026 favicon.png index.xml.gz posts sitemap.xml.gz
165165-404.html favicon.png.gz index.xml.zst pp_ekite_itvw.png sitemap.xml.zst
166166-404.html.gz favicon.png.zst js pp_ekite_itvw.png.gz stats
167167-404.html.zst fonts logo_blue.png pp_ekite_itvw.png.zst tags
168168-ai fr logo_blue.png.gz pp_ekite_itvw_hu_41404e93ad715bdf.webp talks
189189+2020 404.html.zst fonts index.xml.zst projects
190190+2021 ai fr js robots.txt
191191+2022 ai-manifesto icons logo_blue.png series
192192+2023 books images logo_transparent_background.png sitemap.xml
193193+2024 credentials index.html now sitemap.xml.gz
194194+2025 css index.html.gz page sitemap.xml.zst
195195+2026 ekite index.html.zst posts stats
196196+404.html en index.xml pp_ekite_itvw.png tags
197197+404.html.gz favicon.png index.xml.gz pp_ekite_itvw_hu_41404e93ad715bdf.webp talks
198198+```
199199+200200+et vérifier la taille des fichiers compressés :
201201+202202+```bash
203203+$ ls -al public/index.*
204204+.rw-rw-r-- jwittouck jwittouck 33 KB Wed Feb 11 12:15:21 2026 index.html
205205+.rw-rw-r-- jwittouck jwittouck 9.4 KB Wed Feb 11 12:15:21 2026 index.html.gz
206206+.rw-rw-r-- jwittouck jwittouck 9.0 KB Wed Feb 11 12:15:21 2026 index.html.zst
207207+.rw-rw-r-- jwittouck jwittouck 67 KB Wed Feb 11 12:15:22 2026 index.xml
208208+.rw-rw-r-- jwittouck jwittouck 18 KB Wed Feb 11 12:15:22 2026 index.xml.gz
209209+.rw-rw-r-- jwittouck jwittouck 17 KB Wed Feb 11 12:15:22 2026 index.xml.zst
169210```
170211171171-Pour ensuite servir les fichiers précompressés, il faut ajouter la directive `precompressed` dans le `Caddyfile` :
212212+Pour ensuite servir les fichiers précompressés, il faut ajouter la [directive `precompressed`](https://caddyserver.com/docs/caddyfile/directives/file_server#precompressed) dans le `Caddyfile` :
172213173214```Caddyfile
174215# Clever Cloud needs us to listen on port 8080
···177218file_server {
178219 # Clever Cloud serves the public directory
179220 root public
221221+ # serve precompressed files
180222 precompressed
181223}
182224···184226encode
185227```
186228187187-On peut ensuite simplement vérifier que les fichiers compressés sont servis avec une commande `curl`.
229229+La directive `precompressed` recherchera dans l'ordre les fichiers `.zst` et `.gz` pour les servir en priorité, et utilisera comme _fallback_ une compression à la volée.
230230+231231+On peut ensuite simplement vérifier que les fichiers compressés sont bien servis compressés avec une commande `curl`.
188232189233Voici ce qui était renvoyé _avant_ la compression :
190234191235```bash
192192-$ curl --compressed --head https://codeka.io
236236+$ curl --head https://codeka.io
193237194194-HTTP/1.1 200 OK
195195-content-length: 81156
196196-content-type: text/html
197197-accept-ranges: bytes
198198-last-modified: Tue, 03 Feb 2026 12:10:53 GMT
199199-vary: accept-encoding
200200-cache-control: max-age=86400
201201-date: Fri, 06 Feb 2026 16:42:58 GMT
202202-Sozu-Id: 01KGSXBPJWK3D8CZEF8FSPD1Y5
238238+Content-Length: 81157
239239+Content-Type: text/html; charset=utf-8
240240+Server: Caddy
203241```
204242205243et la même commande après la compression :
···207245```bash
208246$ curl --compressed --head https://codeka.io
209247210210-HTTP/1.1 206 Partial Content
211211-Accept-Ranges: bytes
248248+HTTP/1.1 200 OK
212249Content-Encoding: zstd
213213-Content-Length: 9487
214214-Content-Range: bytes 0-9486/9487
215250Content-Type: text/html; charset=utf-8
216216-Etag: "dg80yb3ycgay7bj"
217217-Last-Modified: Fri, 06 Feb 2026 16:37:33 GMT
218251Server: Caddy
219219-Vary: Accept-Encoding
220220-Date: Fri, 06 Feb 2026 16:43:06 GMT
221221-Sozu-Id: 01KGSXBY5TS81HK0BCRSYZSM4S
252252+Content-Length: 9
222253```
254254+255255+[//]: # (TODO) revérifier après le déploiement
223256224257On passe d'une page HTML de 81ko à une donnée compressée de 13ko, sans impacter le CPU du serveur puisque la compression se fait au build !
225258···308341309342J'utilise plausible.io pour suivre les visites de mes articles, donc son script doit pouvoir être chargé. De la même manière, j'ai des iframes (bouh) sur les pages de mes talks qui référencent les videos Youtube ainsi que les feedbacks OpenFeedback.io. Je dois donc aussi autoriser ces ressources.
310343311311-La directive `default-src` sert de fallback pour toutes les directives possible, et indique que seul mon site est autorisé.
344344+La directive `default-src` sert de fallback pour toutes les directives possibles, et indique que seul mon site est autorisé.
345345+346346+## Conclusion
347347+348348+Ça m'a pris une bonne demi-journée pour mettre en place tous ces mécanismes, mais j'en ressort avec une meilleure compréhension de la sécurité et de la compression en HTTP.
349349+J'ai aussi découvert Caddy, et amélioré mon fichier `mise.toml`.
350350+351351+Pour la plupart de mes lecteurs, l'impact de la compression sera probablement minime, car sur des réseaux performants, la différence de temps de chargement ne se ressentira peut-être pas beaucoup.
352352+Mais avec une compression effectuée uniquement au build, c'est aussi une du CPU de moins de consommé, ce qui devrait pouvoir m'assurer de rester sur des instances les plus petites pour mon site le plus longtemps possible.
312353313354## Liens et références
314355315356* Configuration de l'optimisation des images avec Hugo : https://gohugo.io/configuration/imaging/#quality
316357* La méthode [Resize de Hugo](https://gohugo.io/methods/resource/resize/)
317358* [Le hookimage de Hugo](https://gohugo.io/render-hooks/images/#article)
359359+360360+* Documentation de Caddy :
361361+ * [La directive `encode`](https://caddyserver.com/docs/caddyfile/directives/encode#syntax)
362362+ * [La directive `precompressed`](https://caddyserver.com/docs/caddyfile/directives/file_server#precompressed)
318363319364* Documentation MDN :
320365 * [Content-Security-Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Security-Policy)
321366322322-* [Precompressing Content With Hugo and Caddy](https://scottstuff.net/posts/2025/03/09/precompressing-content-with-hugo-and-caddy/)367367+* [Precompressing Content With Hugo and Caddy](https://scottstuff.net/posts/2025/03/09/precompressing-content-with-hugo-and-caddy/)
368368+369369+* L'excellent talk de Antoine Caron et Hubert Sablonière : [La compression Web : comment (re)prendre le contrôle ?](https://www.youtube.com/watch?v=LWd0hr6ljZk)