Implementing Hugo shortcodes
Apr 2019    |    hugo     shortcodes    


Hugo shortcodes are super convenient snippets of code that can be invoked anywhere within your Hugo site. Think of them as embeddable, custom templates.

In a prior version of this website there was a different mechanism of generating the list seen on this page: Go, from around the web

Briefly, a shortcode read in a markdown file containing a list of inline-style links:

[Go for Industrial Programming](https://peter.bourgon.org/go-for-industrial-programming/)

The shortcode snippet looked something like this:

{{$file := .Get "file"}}
    {{- if eq (.Get "markdown") "true" -}}
        {{- $file  | readFile | markdownify -}}
    {{- else -}}
        {{ $file  | readFile | safeHTML }}
{{- end -}}

Which was invoked with:

{\{< readfile file="/layouts/golang/go_blogs.md" markdown="true" >}}

Yikes. The markdown file with all those inline-style links was hard to read and reason about. Also reading a list from one markdown file to another seemed wrong.


So, let’s fix that.

  1. we’ll use a special “data” folder, which Hugo is aware of, to house our “data” that will contain titles, urls, and metadata. We’ll use JSON because it’s easy to parse and work with.
  2. use a shortcode to get at the data folder (which is read by Hugo by default), iterate over it within a shortcode template and then invoke it as before.



At the root of the project we’ll add a Data folder (I called mine Dat to play nice with GitHub pages).

Then we’ll let Hugo know where to look for the data folder by updating the config file with: dataDir: "Dat"

The Dat folder contains go_blogs.jon and looks something like this, a JSON array of objects:

[
    {
        "title": "Go for Industrial Programming",
        "url": "https://peter.bourgon.org/go-for-industrial-programming/",
        "star": true
    }
]

Next, we’ll create a template shortcode for how we’d like the data to be represented once we call the shortcode.

To do so, let’s add a shortcodes folder into the layouts folder and add a file called go_blogs.html

Note, even though the folder in config is called Dat you still call it with .Site.Data. The name of the file: go_blogs (without the extension) contains the data we want..

Here we iterate over those items (from the JSON array):

<ul>
    {{range .Site.Data.go_blogs}}
    <li class="py-1">
        {{if .star}}<i class="fas fa-xs fa-star-half warning-color"></i>{{end}}
        <a href="{{.url}}" target="_blank">{{.title}}</a>
    </li>
    {{end}}
</ul>

Lastly, call the shortcode somewhere within the site:

Note, to prevent calling that shortcode here I broke it intentionally by adding backslash {\\{ otherwise it should be {{.

{\{< go_blogs >}}

And just like that we got a sensible way of adding content through a JSON file and having Hugo do all the heavy lifting.

The added benefit of this approach is being able to conveniently work with the JSON file. E.g., on every deploy we trigger a web hook to a Cloud Function that pulls in the JSON and iterates, concurrently, over all URLs to make sure they are up and running. Otherwise we get a notification to investigate potential link rot.


I had quite a few links (66) in the original markdown file, and doing things manually just isn’t my thing. Thankfully VS Code has great support for regex and multi-line cursors.

Here’s a nifty trick, cmd+f (on mac) then option+cmd+r (mac) to enable regex on search

1 - let’s grab ALL post titles

(?<=\[).+?(?=\])

Then click option+enter to select all of them under a multi-cursor

2 - open empty JSON file, paste the contents of all those titles (66 lines) and start formatting. When finished hop over to the previous file, do another regex for the URLs and repeat.

(?<=\]\().+?(?=\))

Voila, no copy paste.