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:
Read the Go templates documentation.
Understand how to coax Hugo into telling you what the Go template’s
.
is at any given location.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
orresources.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:
- hugo command - github.com/gohugoio/hugo - this is the root of the Go docs for Hugo. It’s not actually that useful.
- hugolib package - github.com/gohugoio/hugo/hugolib - contains a
bunch of the core types, like
Site
. - resources package - github.com/gohugoio/hugo/resources - most
things you find when rendering a template, like
Page
, are in theresources
package somewhere. This is probably the most useful package to read through.
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.
For example, to find out whether .Title
is usable, from page.Page
you have
to traverse four more levels:
- page.Page
- → page.PageWithoutContent
- →
resource.Resource
(because a
Page
is aResource
too!) - → resource.ResourceMetaProvider
- → resource.ResourceNameTitleProvider
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
):
- resource.Resource
- → resource.ResourceWithoutMeta
- →
resource.ResourceTypeProvider
to get a general type, like
image
, or resource.MediaTypeProvider for the more specificimage/png
.
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!