25.6 Minification: resources.Minify
Right, let’s talk about minification. You’ve probably heard the term thrown around like a holy mantra for performance. “Minify your assets!” they shout from the conference stages. And they’re not wrong. But what does it actually mean in Hugo? It means taking your beautifully formatted, human-readable CSS, JS, JSON, HTML, or SVG and ruthlessly stripping out every single unnecessary byte. We’re talking whitespace, comments, and sometimes even shortening variable names. It’s the digital equivalent of vacuum-packing your clothes for a trip. The goal isn’t to be pretty; it’s to be small and fast.
Hugo, in its infinite wisdom, provides a remarkably straightforward way to do this: the resources.Minify function. It’s a pipe, just like the resources.PostProcess or resources.Fingerprint we’ve talked about, which means it plays nicely with the rest of Hugo’s asset processing pipeline. You don’t need to install a separate Node.js toolchain or wrestle with a config file that’s longer than your actual code. Hugo bundles the excellent Go libraries tdewolff/minify and yosssi/gohtml under the hood to handle this heavy lifting. The beauty is its simplicity.
How to Actually Use It
You’ll typically use it in your templates, either on a file you’ve just fetched with resources.Get or on a file that’s already been through another pipe. The syntax is brutally simple.
{{/* Grabs a CSS file and minifies it in one go */}}
{{- $css := resources.Get "css/main.css" | resources.Minify -}}
{{/* Or, more realistically, you minify it after you've processed it with PostCSS */}}
{{- $styles := resources.Get "css/main.scss" | resources.ToCSS | resources.PostProcess | resources.Minify -}}
{{/* Then you pipe it to fingerprinting for cache-busting, because you're not a savage */}}
{{- $styles := $styles | resources.Fingerprint "sha512" -}}
<link rel="stylesheet" href="{{ $styles.Permalink }}" integrity="{{ $styles.Data.Integrity }}">
The same pattern applies for your JavaScript.
{{- $js := resources.Get "js/main.js" | resources.Minify | resources.Fingerprint -}}
<script src="{{ $js.Permalink }}" integrity="{{ $js.Data.Integrity }}" defer></script>
Notice how we’re chaining these operations? That’s the power of the pipe literal (|). The asset flows from one function to the next, getting processed at each step. The order is crucial: you always want to minify before you fingerprint. Fingerprinting generates a unique hash based on the file’s content; if you minify after, you change the content and get a completely different hash, utterly defeating the purpose.
The Gotchas and Rough Edges
Now, let’s be the brilliant friend who tells you the bad news before you hear it from someone else. Hugo’s minification is fantastic, but it’s not a magic wand. It’s a blunt instrument.
First, it’s all-or-nothing. You can’t pass options to tell the minifier, “Hey, leave this particular comment alone because it’s a license header!” Nope. It vaporizes all comments. If you need to preserve a license comment in your CSS or JS, you have a problem. Your options are to either not minify that file (defeating the purpose) or to use a different tool outside of Hugo’s pipeline. It’s a trade-off for simplicity.
Second, and this one has bitten me more than once, minification can break things. It’s exceptionally rare with modern minifiers, but it can happen. The most common culprit is inlining JavaScript or CSS that has ambiguous syntax. The minifier is ruthless and follows its own rules. If your code was already on the edge of being valid, minifying might push it over. This is why you absolutely must test your site with the --minify flag or in production mode (hugo --environment production). Never assume it just works.
Ah, I just mentioned --minify. Here’s the big one: You might not even need to use resources.Minify in your templates! If you start Hugo with the --minify flag, or set minify = true in your site configuration, Hugo will automatically minify every HTML, CSS, JS, JSON, and SVG output it generates. This includes the final, combined output files that are built from your template pipes. So if you’re using that global config, manually minifying an asset in the pipe might be redundant. It’s not harmful, just unnecessary. The global minification is a fantastic feature, but remember, it’s a final-output pass. It won’t minify the individual files you serve directly from the resources pipeline unless you explicitly tell it to with resources.Minify.
So, What’s the Verdict?
Use it. Almost always. The performance gains for textual assets are undeniable and free. The best practice is to set minify = true in your production configuration (config/production/config.toml) to handle your final HTML and any other output, and then use resources.Minify specifically within your asset pipelines for CSS and JS that you’re processing and fingerprinting. This gives you the most control and ensures everything gets squeezed down to its smallest possible size.
Just remember our two rules: 1) Test thoroughly to make sure it doesn’t break your fancy code, and 2) always fingerprint after you minify, never before. Do that, and you’ve just shaved precious kilobytes off your critical path, and your brilliant friend (me) is very proud of you.