Right, let’s talk about concatenation. You’re probably thinking, “Isn’t this just gluing files together?” And you’d be right. But in the world of static sites, doing this efficiently without a live build server (like Webpack) watching your every move is a bit of a superpower. That’s where resources.Concat comes in. It’s your go-to for bundling those pesky little CSS or JS files into a single, cache-friendly, HTTP-request-reducing masterpiece. Think of it as the static site equivalent of duct tape and ambition—but it actually works.

The core idea is simple: you tell Hugo to take a slice of resources (usually sourced from your assets directory) and smush them together into one new file. The magic isn’t in the act of concatenation itself—any script can do that—but in the fact that this new file becomes a first-class Hugo Resource. This means it gets a permalink, you can run it through PostCSS, transpile it with Babel, minify it, fingerprint it… all the good stuff. You’re not just creating a file; you’re creating a file that Hugo’s entire processing pipeline understands.

The Basic Incantation

Here’s how you summon this particular demon. You’ll do this in a template file (like layouts/partials/functions/get-css.html) where the output is a permalink you can stick in your <head>.

{{- with resources.Concat "css/main.bundle.css" -}}
  {{- /* Grab files from the assets directory, order MATTERS! */ -}}
  {{- $normalize := resources.Get "css/vendor/normalize.css" -}}
  {{- $base := resources.Get "css/base.css" -}}
  {{- $components := resources.Get "css/components.css" -}}
  {{- $utilities := resources.Get "css/utilities.css" -}}

  {{- /* Slice them up and concat */ -}}
  {{- $bundle := slice $normalize $base $components $utilities | resources.Concat "css/main.bundle.css" -}}

  {{- /* Now you can process the bundle further */ -}}
  {{- $style := $bundle | resources.PostCSS | minify | fingerprint -}}
  <link rel="stylesheet" href="{{ $style.Permalink }}" integrity="{{ $style.Data.Integrity }}" crossorigin="anonymous">
{{- end -}}

Let’s break this down. First, we grab the individual files using resources.Get. This is crucial: resources.Concat only works on Resource objects, not file paths. We throw them into a slice (Hugo’s term for an array or list) because the function expects a collection. The order of the files in the slice is the exact order they’ll appear in the final bundle. Get this wrong and your CSS specificity will be a nightmare, or your JavaScript will throw a fit because a library loaded before the code that depends on it.

Why This Is a Big Deal

The beauty here is in the next step. We take our new $bundle resource and pipe it through resources.PostCSS and then minify. This means we’re only running these expensive processing steps once on the single bundled file, not on each individual file. The performance gain on a large site is significant. Finally, we fingerprint it (adding a hash to the filename) for cache-busting glory. The whole process is cached by Hugo too, so subsequent builds are lightning fast if the source files don’t change.

Common Pitfalls and the “Oh, C’mon” Moments

Here’s where I save you some frustration.

  1. Source Order is Everything: I said it before, but it’s the number one mistake. If components.css overrides a style in base.css, they must be concatenated in that order. Reverse them and your styles are broken. There’s no dependency management here; you’re the conductor of this orchestra.

  2. Missing Files Crash the Build: If resources.Get can’t find a file, it returns nil. If you try to pass a nil into the slice for resources.Concat, Hugo will throw a fit and your build will fail spectacularly. You must check for missing files if there’s any doubt. A robust version uses with:

    {{- $files := slice -}}
    {{- if (fileExists "assets/css/vendor/normalize.css") -}}
      {{- $files = $files | append (resources.Get "css/vendor/normalize.css") -}}
    {{- end -}}
    {{- $files = $files | append (resources.Get "css/base.css") -}}
    // ... and so on
    {{- $bundle := $files | resources.Concat "css/bundle.css" -}}
    
  3. You Can’t Concat What You Don’t Have: This feels obvious, but it trips people up. You can only concatenate resources Hugo knows about. This typically means files in your assets directory. You can’t easily concatenate files from node_modules unless you copy them into assets first with a build script or Hugo Modules. It’s a bit of a limitation, but it keeps things sane.

  4. It’s Not Just for CSS: While CSS is the most common use case, this works perfectly for JavaScript too. The same rules apply. The resulting JS resource can be piped through js.Build or minified.

The designers made a solid choice here. It’s a low-level, powerful primitive that does one thing well. It doesn’t try to be a full-blown bundler with tree-shaking (that’s what js.Build is for), but it solves the fundamental problem of reducing HTTP requests in a way that’s perfectly integrated with the rest of Hugo’s asset processing pipeline. It’s a workhorse, not a show pony. Use it accordingly.