Build, Minify, and Fingerprint SCSS with Hugo Pipes
For a small Hugo site, I would not add a JavaScript build tool just to compile one SCSS entry point. Hugo Pipes can compile it, create development source maps, compress production output, and add a content hash.
Start with the source and target paths
Put the source file under Hugo’s assets directory, then define its path and the published target:
{{- $cssSource := "scss/main.scss" }}
{{- $cssTarget := "css/style.css" }}
Keep source maps out of production
Use different options for the local server and production build:
{{- $cssOptions := cond (.Site.IsServer) (dict "targetPath" $cssTarget "enableSourceMap" true) (dict "targetPath" $cssTarget "outputStyle" "compressed") }}
When .Site.IsServer is true, Hugo enables a source map. Otherwise it produces compressed CSS.
Fetch the asset and pass it through toCSS:
{{- $style := resources.Get $cssSource | toCSS $cssOptions }}
Then load the generated permalink in the template:
<link rel="stylesheet" href="{{ $style.RelPermalink }}">
The rendered HTML looks roughly like this:
<link rel="stylesheet" href="/css/style.css">
Fingerprinting also solves cache invalidation
Add fingerprint after compilation and output the generated integrity value:
{{- $style := resources.Get $cssSource | toCSS $cssOptions | fingerprint }}
<link rel="stylesheet" href="{{ $style.RelPermalink }}" integrity="{{ $style.Data.Integrity }}">
This will output something like:
<link rel="stylesheet" href="/css/style.9eb7a4aa85871fc4fcf75373921fe7a57fad0b4a7e698cd5861eddfb5ba4584ab7e5.css" integrity="sha256-nrekqoWHH8T891Mf56V/rQtKfmmM1YYYYe3ftbpFhKt+U=">
The hash in the filename changes whenever the CSS changes, so long-lived browser caches can keep old files without hiding a new release. The integrity attribute also lets the browser verify that the downloaded content matches the expected hash.
Keep one entry point, not one giant file
SCSS partials can still separate variables, mixins, components, and layouts:
// main.scss
@import "variables";
@import "mixins";
@import "components/buttons";
@import "layouts/header";
Hugo Pipes also handles other assets. A JavaScript file can be minified and fingerprinted with the same pattern:
{{- $js := resources.Get "js/main.js" | minify | fingerprint }}
<script src="{{ $js.RelPermalink }}" integrity="{{ $js.Data.Integrity }}"></script>
This is enough for many Hugo sites. If the project needs a large JavaScript toolchain, framework-specific transforms, or plugins Hugo does not support, a separate build system may still be justified. I would add it for those requirements, not by default.
Enjoyed reading this?
If you found this article helpful, consider supporting my writing. Every little bit helps keep this blog going.
Related articles
- Customizing Hugo’s RSS Feed
Adjust Hugo RSS titles, descriptions, content, and section filtering while keeping the generated feed valid and easy to discover.
- Organizing Hugo Content With Tags and Categories
Organize Hugo content with a small, deliberate set of tags, categories, or custom taxonomies that lead to useful archive pages.
- A More Useful 404 Page in Hugo
Turn Hugo's 404 page into a useful route back to the homepage, search, or content visitors may have wanted.