Minimizing CSS in Hugo

First target of optimizing my Hugo site was scrubbing the CSS. I’ve been doing some manual cleaning of it, but also having automated scrubbing would be ideal.

Doing this would cross off the first thing on my list

  • Removing unused CSS
  • Minifying/Removing FontAwesome
  • Minimizing requests to 3rd parties (fonts, metrics, etc)
  • Remove Google Fonts while still looking appealing
  • Minimizing use of JavaScript
  • Converting Images to WebP or some other optimized formats

Luckily, Hugo has a postCSS feature that allows some node modules to be run.

Install NPM

First thing is to initialize and install some node modules we’ll need.

npm init
npm install -D --save postcss postcss-cli autoprefixer @fullhuman/postcss-purgecss cssnano

Edit baseof.html

Now we need the hugo site to know it should run the modules. Look for where it’s making the CSS file in your site and add | postCSS to the pipeline.

//baseof.html
{{ $styles := resources.Get "scss/main.scss" | resources.ExecuteAsTemplate "style.main.css" . | toCSS $cssOpts | postCSS | minify | fingerprint }}

Edit config.toml

Next we need to tell hugo that we want a spit out of classnames and tags from from the site when building (so that we know what CSS selectors we need to keep).

//config.toml
[build]
  writeStats = true

This will output into the file hugo_stats.json

Create postcss.config.js

Now we create the postCSS config file. Here is what mine looks like.

//postcss.config.js
module.exports = {
  plugins: {
    '@fullhuman/postcss-purgecss': {
      content: [ './hugo_stats.json' ],
      defaultExtractor: (content) => {
        let els = JSON.parse(content).htmlElements;
        return els.tags.concat(els.classes, els.ids);
      }
    },
    autoprefixer: {},
    cssnano: { preset: 'default' }
  }
};

It basically has 3 modules that are run on CSS.

First is purgecss. This one removes unused selectors from your css. We pass in the hugo_stats.json we configured previously and it handles the rest.

'@fullhuman/postcss-purgecss': {
  content: [ './hugo_stats.json' ],
  defaultExtractor: (content) => {
    let els = JSON.parse(content).htmlElements;
    return els.tags.concat(els.classes, els.ids);
  }
},

Next is autoprefixer, which adds a bunch of prefixes to selectors to help support a majority of browsers.

autoprefixer: {},

And finally cssnano, which does a lot of tweaking and fixes. For example, if you have a hex color with alpha, it will change it to rgba, which is apparently a lot faster.

cssnano: { preset: 'default' }

Build & Summary

Next thing to do is build it.

hugo

Hopefully if everything went well it should clean the CSS without errors.

One thing I ran into was that some random things would just be hidden. So I built the CSS without the postCSS and compared to with it and noticed there was some places where Opacity was being set to 0. Apparently the original CSS had some invalid data so I needed to fix that (opacity: 50% -> opacity: 0.5).