Becoming better at writing Hugo templates

I recently added a new section to the site: Projects. It’s a quick and dirty rendering of some of my favourite things I’ve built over the last twenty-five years. Go check it out 🧙

Building it reminded me that I have never fully become comfortable with Hugo. I find myself almost constantly copy-and-paste editing, either from my own existing templates or from the Internet. I’ve never dug deeply enough into Hugo templates to become confident writing them.

This post is about how I finally did that digging, so I could bring my ideas to life without pulling out so much hair 😖

The overview of getting back into Hugo (ie, “future-Mike should do this when building new bits of site”) is:

  1. Read the Go templates documentation.

  2. Understand how to coax Hugo into telling you what the Go template’s . is at any given location.

  3. Once you know what . is, how do you look up what you can do with it? We’ll look at that last.

    This last one is useful outside of template debugging, as Hugo’s documentation references structs like page.Page but doesn’t always link to the struct’s documentation.

Let’s take each of those in turn and show how to do them.

Read the Go templates documentation

The docs describe the magic-like . (“dot”) variable, along with the primitive template operations available, like range.

Without re-familiarising myself with these, I’m lost. Hugo works by injecting a ton of state into your template and then mostly leaving you to figure out what to do with that. It’s therefore vital to be able to make the distinction between Hugo stuff and Go template stuff.

So read the Go templates documentation already 😛.

Finding the . of your templates

Having now read the Go docs for template, we know . is mainly what we need to know about at any given point in a template.

So how do we find the type of .?

This code is suggested a bunch of times in Hugo’s Discourse forums:

{{ printf "%#v" . }}

For example, in this post. There, this produces something interesting, a type that is public and part of the documentation: hugolib.ShortcodeWithPage. Great!

But when I try it, anywhere in my templates, I seem to instead get the private (and very opaque) hugolib.pageState. Every. Single. Time.

&hugolib.pageState{
  pid:0x338, 
  pageOutputs:[]*hugolib.pageOutput{
    (*hugolib.pageOutput)(0x1400211fa00), 
    (*hugolib.pageOutput)(0x1400211fba0), 
    (*hugolib.pageOutput)(0x1400211fd40)
  }, 
  ... other unintelligable private fields ...
  }

And this pageState type would appear everywhere I looked.

Places where I expected a Page. Or where I thought I should get a PageGroup from a .Pages.GroupByDate call. But instead of a nice, useful public and documented type, it was always pageState. Reading the code got me nowhere.

So I tried to print some of pageState’s fields, and I just got an error thrown back at me:

render: failed to render pages: render of "section" failed: 
"/Users/mike/code/gh/mikerhodes/dx13-hugo/layouts/programs/section.html:15:16": 
execute of template failed at <.pageOutputs>: pageOutputs is an unexported 
field of struct type page.Page

Pah.

Mistakes to the rescue

But wait! What’s that at the end of our error? It’s a real, public, useful type! 🎉 🚀

Here it is again, in case, like me the first few times, you missed it:

pageOutputs is an unexported field of struct type page.Page

So, you can’t dig into a private field of the pageState struct. But trying to do so lets us “see through” this annoying pageState thing to the real type, page.Page, which I can then look up in the Go docs for Hugo. Hooray!

Hugo data type documentation

Once you have the type of struct, you need to know what methods and fields are available. There is the quick place, and the detailed place.

In the Hugo Quick Reference

First the quick place. The quick place to look is in Hugo’s Quick Reference section, where the main functions and structs are described. Before I wrote this post, I’d not noticed this section in Hugo’s docs, and it’s actually really useful! (As I said, this is what happens when you rely on copy-paste rather than going and reading the docs 🤦).

  • Functions | Hugo – describes most of the useful functions Hugo injects into the Go templates.
  • Methods | Hugo – look here to find out what you can do with structs, such as a page.Page or resources.Resource.

But there are a couple of problems with these docs. First, some types, such as the PageGroup that is returned from .Pages.GroupByDate, are not in the Quick Reference docs. Second, they also don’t list every availabe method. For example, a Page is also a Resource, and so has the methods of Resource but you wouldn’t know it from that page.

Digging deeper into the types

Where can we find the full details? First remember that all these things that our . references are just Go types under the hood. So where might we find documentation for the Go structs that Hugo uses? At first I was stumped, and then I had a bit of a 🤦 because you can find them … on the normal Go Docs site at pkg.go.dev:

So that page.Pages struct we talked about earlier has documentation at page.Pages. It tells you that Pages is a []page.Page so you can go to page.Page and find out what you have available. But it’s not quite that simple. The structs in Hugo are … complicated.

So many levels of structs

It turns out that Page is a composite type made up of a bunch of other Go structs, so you generally will need to drill down a few more levels to find some useful raw methods like .Title — ie, ones that you might actually use in your template. Sometimes a lot more levels.

This complexity is why the Quick Reference is usually a better starting place 😬.

For example, to find out whether .Title is usable, from page.Page you have to traverse four more levels:

To find out the type of a raw resource (eg, that you find in . when you are iterating over the image assets for a page using range):

And for now we’re done

I’d still say all this is … not ideal 😂. I still find things hard to find sometimes. But at least I can operate more confidently within Hugo templates now, and that should be useful in future.

Each time I update the site, it feels like I have to re-learn a bunch of Hugo-isms. Hopefully next time I can come back to this post and decrease that learning curve, and maybe even learn some more.

Bonus points if I remember to write those things down too!

← Older
Airpods Pro: final thoughts