Head to Head: Shortcodes vs Partials in Hugo

Reusable snippets

Reusable snippets - that’s what programming is all about. I just spent all day learning about the difference between shortcodes and parials, and more than once told myself “I could just hardcode all of this and it would take thirty minutes… why again am I doing this?”

But there is a strong case for snippets, blocks, components… or whatever you call them:

In Hugo, they are called partials and shortcodes, and no, the two are not interchangeable. Here’s everything I learned about them on a recent project.

What started it

Here’s the setup: building a multilingual site for a church, I needed a card that would show a staff member’s image, name and title. The name and title could vary depending on which site language was being used (korean & english). Unfortunately, English spellings for Korean names are ridiculous. The same name will often be spelled half a dozen different ways, and since it’s a name people can very sensitive about having it spelled the way they spell it. There is no way I could ensure consistency without having a single source of truth.

Hugo does have a data folder for stuff like this. Unlike everything in the content folder, its contents don’t get compiled into separate pages. Data files can be called up and used like data objects from a database.

For the staff directory, it was clear I needed to create a component that would consult a data source to look up image urls, name strings, and any other fields that might get included. I decided to create one data file per staff member and generate the staff directory from a simple list of ids - all solid decisions, but things got difficult in the implementation.

Simple breakdown of shortcodes vs partials

shortcodes partials
usable in /content/*.md files usable in /layout/*.html files
can access page context receive context scope as a parameter

Personally I don’t like a hard distinction between these two. When they work in such similar ways, why the separation? If anybody could give me a clear explanation (or even a few hints), I would be grateful. I’ll get into a counter argument in a different post, but suffice it to say I understand the differences but still don’t understand the reasoning behind them.

Examples

After lots of time reading documentation, desparately searching stack overflow, and finally just trying a lot of different things and seeing what pieces worked, I got my staff info card working. This is the final code I came up with, both shortcode and partial versions. I’ll highlight the differences below.

As a shortcode

<div class="headshot">
  <figure>
    {{ $data := index $.Site.Data.people (index .Params 0)}}
    <img src='{{ (print "/img/" $data.headshot) }}' >
    <figcaption>
      {{ if eq $.Site.Language.Lang "en" }}
        <h4>
          {{ $data.name.en }} <span>{{ $data.title.en }}</span>
        </h4>
      {{ else }}
        <h4>
          {{ $data.name.ko }} <span>{{ $data.title.ko }}</span>
        </h4>  
      {{ end }}
    </figcaption>
  </figure>
</div>

It gets called from a content file like this {{< headshot kim_keecheon >}}.

Shortcodes can be written to accept any number of parameters, named or unnamed. For anything more than one parameter though, I would strongly recommend naming the parameters. Remember, we are assuming shortcodes will be used by the end user thus probably not a coder. Making people remember parameters and the order they have to go in just isn’t nice. Shortcodes can also be written as a set of tags with enclosed content, which would look like this {{< headshot kim_keecheon >}}This is the bio that goes inside{{< /headshot >}}.

As a partial

<div class="headshot">
  <figure>
    {{ $data := index .ctx.Site.Data.people .id}}
    <img src='{{ (print "/img/" $data.headshot) }}' >
    <figcaption>
      {{ if eq .ctx.Site.Language.Lang "en" }}
        <h4>
          {{ $data.title.en }} <span>{{ $data.name.en }}</span>
        </h4>
      {{ else }}
        <h4>
          {{ $data.title.ko }} <span>{{ $data.name.ko }}</span>
        </h4>  
      {{ end }}
    </figcaption>
  </figure>
</div>

Notice the only difference is in line 3. This is because of the difference in context and parameters. It is called from a template file like this, {{ partial “headshot” (dict “ctx” $ “id” .)}}.

The name of the partial must be in quotes but the .html ending is optional. The staff id is being pulled in from the content file (although it could be defined in the template) in a loop. Within that loop, the context - in Go and Hugo held in the variable . (dot) - would not be the page, it would be the object whose list is being looped over. In this case, simply a list of user ids, so I am passing the global context using the $ sign and the id using the dot.

This took me a long time and some serious debugging to catch. It is stated in the documentation, but to be fair, it is easily missed.

…the partial will only be able to access those variables. The partial is isolated and has no access to the outer scope. From within the partial, $.Var is equivalent to .Var. source

Typically the $ sign is bound to the global context, but within a partial this isn’t true.

Bonus: using the index function

Untangling this problem was complicated by trying to figure out whether I needed to pass strings or objects. The user ids were saved as strings, so at some point, they needed to be evaluated to retreive the correct data object. I came across a lot of confusing or misleading talk on stack overflow about using {{ printf $.Site.Data “test” }} to simply concatenate the data file name (id) on the end.

The solution is using the index function. There are some examples in the documentation of using it precisely this way: read them.