CINXE.COM
Feuilles de style minifiées et gestion du cache - Nicolas Friedli
<!doctype html><html lang="fr"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width,initial-scale=1"> <title>Feuilles de style minifiées et gestion du cache - Nicolas Friedli</title> <meta name="description" content="Ce que j’applique en général pour la minification et la mise en cache des feuilles de style. C’est une optimisation raisonnable qui utilise certaines fonctions natives d’Hugo mais évite trop de complexité."> <style>html{font-family:ui-sans-serif,fira sans,roboto,inter,system-ui,sans-serif;line-height:1.6;font-variant-numeric:oldstyle-nums proportional-nums}code,pre{font-family:ui-monospace,jetbrains mono,roboto mono,hack,menlo,consolas,cascadia code,monospace;font-variant-ligatures:none}code,pre,table{font-variant-numeric:lining-nums tabular-nums}h1,h2,h3,h4{line-height:1.1;text-wrap:balance}a{font-variant-numeric:lining-nums proportional-nums}</style> <style media="screen">:root{--dark:#000;--light:#FFF;--lightgray:#EEE}*{scrollbar-color:var(--dark)var(--light);scrollbar-width:thin}*,*::before,*::after{box-sizing:border-box}body,h1,h2,h3,h4,p,figure,blockquote,dl,dd{margin-block-end:0}img,picture{max-width:100%;display:block}html{font-size:clamp(1rem,.9565rem + .2174vw,1.125rem)}html,body{margin:0;padding:0}body{display:flex;flex-direction:column;min-height:100vh;background:var(--light);color:var(--dark)}header,footer{background:var(--dark);color:var(--light);font-size:90%}footer{margin-top:auto}h1,h2,h3,h4{margin-block:2rem 1rem}h1{font-size:2.2rem;font-weight:600}h2{font-size:1.7rem;font-weight:700}h3{font-size:1.3rem;font-weight:800}h4{font-size:1rem;font-weight:900;text-transform:uppercase}a{color:inherit;text-decoration-thickness:.1em;text-underline-offset:.1em}del{background:var(--lightgray);text-decoration-thickness:.1em}p{margin-block:1rem}ol,ul{padding-left:1rem}::marker{font-weight:700}li{padding-top:.5rem}li p{margin-top:0}dt{font-weight:900}dd+dt{margin-top:1rem}dd{margin-inline-start:2rem}code,pre{background:var(--lightgray)}:not(pre)>code{font-size:90%}pre{font-size:85%;overflow-x:auto;margin-block:1rem 2rem;padding:.5rem 1rem}hr{border:0;border-top:.5rem solid var(--lightgray);margin-block:2rem}.time{margin-block:2rem;font-size:90%}main :is(p,blockquote,ul :not(grid),ol,dl){width:min(45rem,100%)}.wrapper{margin-inline:auto;width:min(60rem,100%)}.padding{padding:1rem}blockquote{border-inline-start:.2rem solid var(--dark);padding-inline:1rem;margin-inline-start:0;font-style:italic}blockquote em{font-style:normal}.flex{display:flex;flex-wrap:wrap;gap:1rem;justify-content:space-between}.grid{list-style-type:none}ul.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(25ch,1fr));gap:1rem;margin:0;margin-block:1rem;padding:0}.grid li{background:var(--lightgray);border-radius:.2rem;margin:0;padding:1rem}li.article{display:flex;gap:.5rem;flex-direction:column;justify-content:space-between;text-wrap:balance}.article a{font-weight:600}.article time{font-size:.9rem}.nf{font-weight:900}</style> <style media="print">@page{margin:2cm}header,footer{display:none}pre{white-space:pre-wrap}a::after{content:" [" attr(href)"] "}</style> <link rel="icon" type="image/png" href="/favicon-96x96.png" sizes="96x96"> <link rel="icon" type="image/svg+xml" href="/favicon.svg"> <link rel="shortcut icon" href="/favicon.ico"> <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png"> <meta name="apple-mobile-web-app-title" content="NFriedli"> <link rel="manifest" href="/site.webmanifest"> <link rel="alternate" type="application/rss+xml" href="/index.xml" title="Nicolas Friedli"> <link rel="canonical" href="https://nicolasfriedli.ch/blog/css-minification-cache/"> <script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"Feuilles de style minifiées et gestion du cache","image":["/images/opengraph.jpg","/images/nicolas-friedli.jpg"],"wordCount":"715","datePublished":"2024-03-15","dateModified":"2024-10-17","author":[{"@type":"Person","name":"Nicolas Friedli","url":"https://nicolasfriedli.ch/"}],"isAccessibleForFree":true}</script> <script defer data-domain="nicolasfriedli.ch" src="https://plausible.io/js/script.outbound-links.tagged-events.js"></script> <link rel="webmention" href="https://webmention.io/nicolasfriedli.ch/webmention"> <link rel="pingback" href="https://webmention.io/nicolasfriedli.ch/xmlrpc"> <link rel="me" href="https://mastodon.social/@nicolasfriedli"> <meta name="fediverse:creator" content="@nicolasfriedli@mastodon.social"> <meta property="og:url" content="https://nicolasfriedli.ch/blog/css-minification-cache/"> <meta property="og:site_name" content="Nicolas Friedli"> <meta property="og:title" content="Feuilles de style minifiées et gestion du cache"> <meta property="og:description" content="Ce que j’applique en général pour la minification et la mise en cache des feuilles de style. C’est une optimisation raisonnable qui utilise certaines fonctions natives d’Hugo mais évite trop de complexité."> <meta property="og:locale" content="fr"> <meta property="og:type" content="article"> <meta property="article:section" content="blog"> <meta property="article:published_time" content="2024-03-15T00:00:00+00:00"> <meta property="article:modified_time" content="2024-10-17T00:00:00+00:00"> <meta property="og:image" content="https://nicolasfriedli.ch/images/opengraph.jpg"> </head> <body> <header> <div class="wrapper"> <div class="padding"> <div class="flex"> <a href="/" class="nf">Nicolas Friedli</a> <nav class="flex"> <a href="/blog/">Blog</a> <a href="/blogroll/">Liens</a> <a href="/search/">Recherche</a> <a href="/contact/">Contact</a> </nav> </div> </div> </div> </header> <div class="wrapper"> <main class="padding" data-pagefind-body> <h1>Feuilles de style minifiées et gestion du cache</h1> <div class="time"> <time datetime="2024-03-15" class="entry-date dateline">15 mars 2024</time> <time datetime="2024-10-17" class="entry-date dateline">/ dernière modification: 17 octobre 2024</time> </div> <p>Ce que j’applique en général pour la minification et la mise en cache des feuilles de style. C’est une <em>optimisation raisonnable</em> qui utilise certaines fonctions natives d’Hugo mais évite trop de complexité.</p> <h2 id="utiliser-une-feuille-de-style-externe">Utiliser une feuille de style externe</h2> <p>La version la plus simple pour déclarer une feuille de style CSS, c’est un appel dans l’entête HTML:</p> <pre tabindex="0"><code><link rel="stylesheet" href="/css/style.css"> </code></pre><p>Pour que la feuille de style se trouve bien à l’endroit référencé après une compilation par Hugo, on la place dans: <code>/static/css/style.css</code>.</p> <p>Le fichier est seulement copié, sans aucune modification, et placé dans le répertoire du site compilé: <code>/public/css/style.css</code>. C’est le principe de fonctionnement pour les <a href="https://gohugo.io/getting-started/directory-structure/#static">fichiers statiques</a> chez Hugo. Tout ce qui se trouve dans le répertoire <code>/static/</code> est copié tel quel, avec la même hiérarchie, dans <code>/public/</code>.</p> <h2 id="minification-des-css-par-hugo">Minification des CSS par Hugo</h2> <p>C’est une bonne idée de minifier les feuilles de style. La copie seule est alors insuffisante (à moins de placer un fichier minifié par un outil tiers dans <code>/static/</code>, évidemment) Je place donc la feuille de style de départ dans <code>/assets/css/screen.css</code>. Comme son nom l’indique, elle est spécifique à l’affichage sur un écran.</p> <p>Le répertoire <code>/assets/</code> permet d’effectuer des opérations sur les fichiers qu’il contient. Ici:</p> <pre tabindex="0"><code>{{ $screen := resources.Get "css/screen.css" | minify }} <link rel="stylesheet" href="{{ $screen.RelPermalink }}" media="screen"> </code></pre><p>La première ligne va chercher le fichier, relativement au répertoire, <code>/assets/</code> et le minifie. J’utilise pour cela un filtre. La seconde ligne appelle le fichier final en HTML. Le fichier produit sera <code>/public/css/screen.min.css</code>.</p> <p>Je procède de même pour la feuille de style dédiée à l’impression:</p> <pre tabindex="0"><code>{{ $print := resources.Get "css/print.css" | minify }} <link rel="stylesheet" href="{{ $print.RelPermalink }}" media="print"> </code></pre><p>Le fichier produit se trouvera au même endroit que celui dédié à l’impression. Son nom portera, lui aussi, la trace de la minification par <code>.min.</code>.</p> <h2 id="fingerprint-et-cache-long">Fingerprint et cache long</h2> <p>Il est possible de mettre en cache les feuilles de style pour une très longue durée. C’est pourquoi je souhaite que le nom de fichier final soit unique. C’est facile avec <a href="https://gohugo.io/hugo-pipes/fingerprint/">fingerprint</a>:</p> <pre tabindex="0"><code>{{ $screen := resources.Get "css/screen.css" | minify | fingerprint }} <link rel="stylesheet" href="{{ $screen.RelPermalink }}" media="screen"> </code></pre><p>Ainsi, le nom final sera quelque chose comme:</p> <pre tabindex="0"><code>/css/screen.min.215a9ff1ab8351ee4d0a3c644904e7ab4945a1fa3d70197a0735fc3e43195476.css` </code></pre><p>Double avantage de la démarche:</p> <ul> <li>la certitude que la feuille de style qui s’applique à la page actuelle est toujours la bonne (et jamais une autre CSS en mémoire dans le navigateur)</li> <li>la possibilité de proposer une durée de cache très longue pour ne jamais recharger la feuille de style si elle existe déjà dans le navigateur</li> </ul> <p>Cela se passe dans <code>.htaccess</code> avec un serveur Apache:</p> <pre tabindex="0"><code><filesMatch ".(css)$"> Header set Cache-Control "max-age=63072000,immutable" </filesMatch> </code></pre><p>Ou dans <code>_headers</code> chez Netlify:</p> <pre tabindex="0"><code>/css/* Cache-Control: max-age=63072000,immutable </code></pre><p>Tant que le style n’a pas été modifié, le fichier n’est pas téléchargé. C’est clair et efficace.</p> <h2 id="integrer-la-feuille-de-style">Intégrer la feuille de style</h2> <p>Sur ce site, j’intégre le CSS dans le code HTML. C’est favorable lors de la première visite d’<em>une</em> page:</p> <pre tabindex="0"><code>{{ $screen := resources.Get "css/screen.css" }} <style media="screen">{{ $screen | safeCSS }}</style> </code></pre><p>Mais c’est potentiellement contre-productif si l’internaute lit plusieurs pages ou revient sur le site. Toutefois, le gain d’une solution ou de l’autre est toujours minime sur un site léger comme le mien.</p> <p>Quand le style est intégré, on pourrait aller plus loin et nettoyer <em>chaque page</em> de ses styles inutiles. C’est ce que fait Max Böck avec son <a href="https://mxb.dev/blog/emergency-website-kit/">site d’urgence avec Eleventy (11ty)</a>. C’est impossible avec Hugo sans outils externes et le gain est marginal.</p> <h2 id="outils-hugo-non-utilises-sur-mon-blog">Outils Hugo non utilisés sur mon blog</h2> <p>Il me semble que les options choisies sont pertinentes et suffisantes pour ce site. Mais Hugo propose d’autres possibilités. Par exemple:</p> <ul> <li>Réunir des feuilles de style avec <a href="https://gohugo.io/hugo-pipes/bundling/">Concat</a>. C’est inutile puisque je ne n’ai qu’un fichier (par type de média).</li> <li>Ajouter une vérification par <a href="https://gohugo.io/hugo-pipes/fingerprint/#usage">Subresource Integrity</a>. C’est pertinent si l’on dépose ses CSS sur un serveur tiers (par exemple un CDN) et que l’on veut être certain qu’elles n’ont pas été modifiées.</li> <li>Traiter la feuille de style avec SASS par la commande <a href="https://gohugo.io/hugo-pipes/transpile-sass-to-css/">ToCSS</a>. Je préfère l’utilisation des variables CSS. La version de SASS embarquée dans Hugo n’est plus mise à jour. Et la version Dart exige des dépendances externes.</li> <li><a href="https://zwbetz.com/how-to-use-purgecss-with-hugo/">Supprimer les styles non utilisés avec PurgeCSS</a> comme le propose Zachary Wade Betz. C’est utile sur un site avec une grande feuille de style (par exemple un <em>framework</em> CSS), mais pas ici.</li> </ul> <hr> <aside data-pagefind-ignore> <p>Dans la même thématique:</p> <ul> <li><a href="/blog/hugo-chroma-colorisation-syntaxique/">Colorisation syntaxique avec Chroma dans Hugo</a></li> <li><a href="/blog/linkmail/">Utiliser des liens mail préremplis sur son site web</a></li> <li><a href="/blog/budget-performance/">Travailler avec un budget de performance web</a></li> <li><a href="/blog/google-fonts-demande/">Activer les polices Google Fonts à la demande</a></li> <li><a href="/blog/hugo-pages-soeurs-filles/">Pages sœurs, filles et mère avec Hugo</a></li> </ul> </aside> </main> </div> <footer> <div class="wrapper"> <div class="padding"> Abonnement: <a href="/index.xml">flux RSS</a>. Commentaires: <a href="mailto:hello+2025@nicolasfriedli.ch">hello+2025@nicolasfriedli.ch</a>. Licence: Creative Commons BY-SA. </div> </div> </footer> </body> </html>