Michele Titolo | Blog | Speaking

Mixing content and data in Hugo

A few months ago I switched this site over to the static-site generator Hugo from a flat-file CMS. It was great. In half a day I had ported my HTML and CSS to a Hugo theme and setup CircleCI to build and publish to S3. I’ve now started using Hugo for other things, which in some ways stretch the limits of what it can do.

In one project, the content pages have corresponding data files. If you’re unfamiliar with kinds of things that Hugo can render, the TL;DR is that it can serve markdown content and data from YAML and JSON files, but they are expected to be shown separately or as a one-off combination (my speaking page is an example of a one-off). I needed to mix the two, and specify a data file in the frontmatter of different markdown files so that data would show up on the page.

The big problem is that data is only available via the $.Site.Data variable in a Hugo template. So if you had a data file called bands.yaml, it would be accessed in a template by calling $.Site.Data.bands. I tried several different ways of making that dynamic. The first step was adding a frontmatter variable. I called mine data_file and it’s accessed via .Params.data_file in the templates.

My first attempt at using this variable was to treat $.Site.Data as I would any other hash. This did not work.

# some-page.md
---
data_file: bands
---
// layouts/some-page/single.html
{{ range $.Site.Data[.Params.data_file] }} // Syntax error unexpected [
...
{{ end }}

The second thing I tried was putting the site variable in the frontmatter of the page. I didn’t expect this to work, but I figured I’d try anyways. It did not work.

# some-page.md
---
data_file: $.Site.Data.bands
---
// layouts/some-page/single.html
{{ range .Params.data_file }} // Error: Wrong type for range
...
{{ end }}

Then I started poking around the Hugo forums, and found a post on dynamically generating paths to partials. Now we are getting somewhere! This made me realize two things:

  1. Every data file I want to use needs its own partial, so $.Site.Data.whatever can be hardcoded. There’s no way with Hugo or Go to dynamically generate a variable (which is possible in other languages like Objective-C)
  2. I can compose template commands with code

Progress.

After more poking around, here’s the working solution I came up with:

# some-page.md
---
data_file: bands_partial.html
---
// layouts/some-page/single.html
{{ $data_partial := (partial (printf "%s" .Params.data_file) .) }}
{{ $data_partial }}
// bands_partial.html
{{ range $.Site.Data.bands }}
...
{{ end }}

This works because the partial function expects to be given a string, which can be printed from the .Params.data_file string pointer.

Ét Voilà! By specifying a partial name in the frontmatter, I can now reuse the single.html template and load a different data file per content page, as needed. Each data file does need its own partial, which is not ideal.

This technique can be reused for any other kind of partial and is not just limited to my use case with data. In the future I can see using this less for data and more for pages with other kinds of mix-and-match components.

© 2023 Michele Titolo