Source code of my website
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

🚧 : final draft version before publication

+93 -46
+93 -46
content/posts/drafts/2026-02-06-optimiser-site-statique/index.md
··· 30 30 31 31 > J'ai clairement une marge d'amélioration sur l'accessibilité et les performances. 32 32 33 + ## Minification 34 + 35 + Une première étape consiste à minifier les ressources statiques, HTML, CSS et JS. 36 + 37 + Cette étape est très simple à mettre en place, car elle est déjà supportée par Hugo. 38 + Il suffit lors du build d'ajouter le flag `--minify` pour demander à Hugo de minifier toutes les ressources. 39 + 40 + Ma commande de build est la suivante dans mon `mise.toml` : 41 + 42 + ```toml 43 + [tasks.build] 44 + description = "Build le site avec Hugo" 45 + run = "hugo --gc --minify --destination public" 46 + ``` 47 + 48 + Ce qui produit des fichiers HTML minifiés de ce type : 49 + 50 + ```html 51 + <!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"> 52 + ``` 53 + 54 + Hop, on peut passer rapidement à autre chose 🚶 55 + 33 56 ## Conversion des images en webp et redimensionnement 34 57 35 58 Une 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. ··· 38 61 39 62 Ces 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_. 40 63 41 - Hugo supporte la recompression des images dans différents formats à la volée, mais pas leur redimensionnement automatique. 64 + 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. 42 65 Pour 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. 43 66 44 67 Le hook utilisé par défaut est le suivant : ··· 64 87 {{- end -}} 65 88 {{- /* chomp trailing newline */ -}} 66 89 ``` 90 + 91 + 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. 67 92 68 93 ## Pré-compression des ressources statiques 69 94 95 + 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). 96 + 97 + Avant de passer à la pré-compression en elle-même, il faut regarder comment les ressources seront servies. 98 + 70 99 Mon site est hébergé chez Clever Cloud, dans une instance de type _static_. 71 100 J'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). 72 101 ··· 87 116 encode 88 117 ``` 89 118 90 - 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`. 119 + 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). 91 120 92 121 Cette compression permet d'économiser de la bande passante et accélère le temps de chargement des pages. 93 122 94 123 Cependant, la compression se fait en utilisant un peu de CPU à la volée. 95 124 Il est alors intéressant de pré-compresser les ressources statiques à la phase de build pour économiser un peu de CPU. 96 125 97 - Un directive Caddy permet de servir des fichiers statiques pré-compressés : `precompressed`. 126 + Une directive Caddy permet de servir des fichiers statiques pré-compressés : `precompressed`. 98 127 Caddy va alors rechercher des variantes compressées des fichiers, sous la forme de fichiers sidecar. 99 - À 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. 128 + À 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. 100 129 101 - 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. 130 + 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. 102 131 103 - J'ai donc créé ce script dans mon fichier `mise.toml`: 132 + J'ai donc créé ce script dans mon fichier `mise.toml` : 104 133 105 134 ```toml 106 135 [tasks.build] ··· 114 143 [tasks.precompress] 115 144 description = "Precompress static resources" 116 145 run = ''' 117 - COMPRESSREGEX=".*(html|css|js|xml|ico|svg|md|png|webp|pdf)$" 118 - find public/ -type f -regextype egrep -regex $COMPRESSREGEX | xargs zstd -15 -f 119 - find public/ -type f -regextype egrep -regex $COMPRESSREGEX | xargs zstd --format=gzip -15 -f 146 + COMPRESSREGEX=".*(html|css|js|xml|ico|svg|md|pdf|woff2)$" 147 + find public/ -type f -regextype egrep -regex $COMPRESSREGEX | xargs zstd --keep --force -19 148 + find public/ -type f -regextype egrep -regex $COMPRESSREGEX | xargs gzip --keep --force --best 120 149 ''' 121 150 ``` 151 + 152 + 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. 153 + 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. 122 154 123 155 Par 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. 124 156 ··· 145 177 Cleaned │ 0 │ 0 146 178 147 179 Total in 272 ms 148 - [precompress] $ COMPRESSREGEX=".*(html|css|js|xml|ico|svg|md|png|webp|pdf)$" 149 - 593 files compressed : 90.10% ( 165 MiB => 149 MiB) B ==> 98%% 150 - 593 files compressed : 93.31% ( 165 MiB => 154 MiB) 180 + [precompress] $ COMPRESSREGEX=".*(html|css|js|xml|ico|svg|md|pdf|woff2)$" 181 + 245 files compressed : 80.99% ( 83.3 MiB => 67.4 MiB) B ==> 98%^T 151 182 Finished in 7.77s 152 183 ``` 153 184 ··· 155 186 156 187 ```bash 157 188 $ ls public/ 158 - 2020 ai-manifesto icons logo_blue.png.zst pp_ekite_itvw_hu_41404e93ad715bdf.webp.gz 159 - 2021 books images logo_transparent_background.png pp_ekite_itvw_hu_41404e93ad715bdf.webp.zst 160 - 2022 credentials index.html logo_transparent_background.png.gz projects 161 - 2023 css index.html.gz logo_transparent_background.png.zst robots.txt 162 - 2024 ekite index.html.zst now series 163 - 2025 en index.xml page sitemap.xml 164 - 2026 favicon.png index.xml.gz posts sitemap.xml.gz 165 - 404.html favicon.png.gz index.xml.zst pp_ekite_itvw.png sitemap.xml.zst 166 - 404.html.gz favicon.png.zst js pp_ekite_itvw.png.gz stats 167 - 404.html.zst fonts logo_blue.png pp_ekite_itvw.png.zst tags 168 - ai fr logo_blue.png.gz pp_ekite_itvw_hu_41404e93ad715bdf.webp talks 189 + 2020 404.html.zst fonts index.xml.zst projects 190 + 2021 ai fr js robots.txt 191 + 2022 ai-manifesto icons logo_blue.png series 192 + 2023 books images logo_transparent_background.png sitemap.xml 193 + 2024 credentials index.html now sitemap.xml.gz 194 + 2025 css index.html.gz page sitemap.xml.zst 195 + 2026 ekite index.html.zst posts stats 196 + 404.html en index.xml pp_ekite_itvw.png tags 197 + 404.html.gz favicon.png index.xml.gz pp_ekite_itvw_hu_41404e93ad715bdf.webp talks 198 + ``` 199 + 200 + et vérifier la taille des fichiers compressés : 201 + 202 + ```bash 203 + $ ls -al public/index.* 204 + .rw-rw-r-- jwittouck jwittouck 33 KB Wed Feb 11 12:15:21 2026 index.html 205 + .rw-rw-r-- jwittouck jwittouck 9.4 KB Wed Feb 11 12:15:21 2026 index.html.gz 206 + .rw-rw-r-- jwittouck jwittouck 9.0 KB Wed Feb 11 12:15:21 2026 index.html.zst 207 + .rw-rw-r-- jwittouck jwittouck 67 KB Wed Feb 11 12:15:22 2026 index.xml 208 + .rw-rw-r-- jwittouck jwittouck 18 KB Wed Feb 11 12:15:22 2026 index.xml.gz 209 + .rw-rw-r-- jwittouck jwittouck 17 KB Wed Feb 11 12:15:22 2026 index.xml.zst 169 210 ``` 170 211 171 - Pour ensuite servir les fichiers précompressés, il faut ajouter la directive `precompressed` dans le `Caddyfile` : 212 + 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` : 172 213 173 214 ```Caddyfile 174 215 # Clever Cloud needs us to listen on port 8080 ··· 177 218 file_server { 178 219 # Clever Cloud serves the public directory 179 220 root public 221 + # serve precompressed files 180 222 precompressed 181 223 } 182 224 ··· 184 226 encode 185 227 ``` 186 228 187 - On peut ensuite simplement vérifier que les fichiers compressés sont servis avec une commande `curl`. 229 + 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. 230 + 231 + On peut ensuite simplement vérifier que les fichiers compressés sont bien servis compressés avec une commande `curl`. 188 232 189 233 Voici ce qui était renvoyé _avant_ la compression : 190 234 191 235 ```bash 192 - $ curl --compressed --head https://codeka.io 236 + $ curl --head https://codeka.io 193 237 194 - HTTP/1.1 200 OK 195 - content-length: 81156 196 - content-type: text/html 197 - accept-ranges: bytes 198 - last-modified: Tue, 03 Feb 2026 12:10:53 GMT 199 - vary: accept-encoding 200 - cache-control: max-age=86400 201 - date: Fri, 06 Feb 2026 16:42:58 GMT 202 - Sozu-Id: 01KGSXBPJWK3D8CZEF8FSPD1Y5 238 + Content-Length: 81157 239 + Content-Type: text/html; charset=utf-8 240 + Server: Caddy 203 241 ``` 204 242 205 243 et la même commande après la compression : ··· 207 245 ```bash 208 246 $ curl --compressed --head https://codeka.io 209 247 210 - HTTP/1.1 206 Partial Content 211 - Accept-Ranges: bytes 248 + HTTP/1.1 200 OK 212 249 Content-Encoding: zstd 213 - Content-Length: 9487 214 - Content-Range: bytes 0-9486/9487 215 250 Content-Type: text/html; charset=utf-8 216 - Etag: "dg80yb3ycgay7bj" 217 - Last-Modified: Fri, 06 Feb 2026 16:37:33 GMT 218 251 Server: Caddy 219 - Vary: Accept-Encoding 220 - Date: Fri, 06 Feb 2026 16:43:06 GMT 221 - Sozu-Id: 01KGSXBY5TS81HK0BCRSYZSM4S 252 + Content-Length: 9 222 253 ``` 254 + 255 + [//]: # (TODO) revérifier après le déploiement 223 256 224 257 On 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 ! 225 258 ··· 308 341 309 342 J'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. 310 343 311 - La directive `default-src` sert de fallback pour toutes les directives possible, et indique que seul mon site est autorisé. 344 + La directive `default-src` sert de fallback pour toutes les directives possibles, et indique que seul mon site est autorisé. 345 + 346 + ## Conclusion 347 + 348 + Ç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. 349 + J'ai aussi découvert Caddy, et amélioré mon fichier `mise.toml`. 350 + 351 + 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. 352 + 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. 312 353 313 354 ## Liens et références 314 355 315 356 * Configuration de l'optimisation des images avec Hugo : https://gohugo.io/configuration/imaging/#quality 316 357 * La méthode [Resize de Hugo](https://gohugo.io/methods/resource/resize/) 317 358 * [Le hookimage de Hugo](https://gohugo.io/render-hooks/images/#article) 359 + 360 + * Documentation de Caddy : 361 + * [La directive `encode`](https://caddyserver.com/docs/caddyfile/directives/encode#syntax) 362 + * [La directive `precompressed`](https://caddyserver.com/docs/caddyfile/directives/file_server#precompressed) 318 363 319 364 * Documentation MDN : 320 365 * [Content-Security-Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Security-Policy) 321 366 322 - * [Precompressing Content With Hugo and Caddy](https://scottstuff.net/posts/2025/03/09/precompressing-content-with-hugo-and-caddy/) 367 + * [Precompressing Content With Hugo and Caddy](https://scottstuff.net/posts/2025/03/09/precompressing-content-with-hugo-and-caddy/) 368 + 369 + * 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)