Hugo Advanced: Shortcodes, Partials, and Making Your Static Site Feel Dynamic

The template machinery that turns a flat folder of Markdown into something that breathes

Most people meet Hugo as “the thing that turns Markdown into HTML very fast”, install a theme, and stop there. That is a perfectly good place to stop. But underneath the convenience sits a real templating system, and once you learn to drive it, a static site can do a surprising amount of what people reach for JavaScript frameworks to achieve — without shipping a single byte of runtime to the browser. The trick is that all the dynamism happens at build time. The visitor gets plain HTML; the cleverness was spent before they ever arrived.

Advertisement

A partial is a reusable template fragment. If you have ever copied a chunk of HTML between two layouts and felt dirty about it, a partial is the cure. They live in layouts/partials/ and you pull them in with a single function call, passing whatever context they need.

{{ partial "author-card.html" .Params.author }}

The genuinely useful part is the second argument: you decide what context the partial sees. Pass it the whole page with ., or a single value, or a custom dict you assemble on the spot with (dict "title" .Title "count" 5). A well-factored Hugo theme is mostly partials calling partials, each receiving exactly the data it needs and nothing more.

There is also partialCached, which renders a partial once and reuses the result across every page that asks for it. For a sidebar or footer that is identical site-wide, this is free performance: on a site with hundreds of pages, computing the footer once instead of hundreds of times is the difference between a build that feels instant and one that makes you go for coffee.

Partials live in templates. Shortcodes live in your content. A shortcode is a small template you invoke from inside a Markdown file, which lets writers do things Markdown cannot express without dropping into raw HTML and ruining the readability of the source.

You define one in layouts/shortcodes/. Here is a simple notice box:

{{/* layouts/shortcodes/notice.html */}}
<div class="notice notice-{{ .Get 0 }}">
  {{ .Inner | markdownify }}
</div>

And in the Markdown, a writer uses it like this:

{{% notice warning %}}
This API is deprecated and will be removed in the next release.
{{% /notice %}}

The .Inner is whatever sits between the opening and closing tags, and markdownify means the writer can still use Markdown inside it. Suddenly your content authors have a vocabulary — notices, figures with captions, embeds, pricing tables — that stays readable in the source and renders consistently everywhere. That is a quietly enormous improvement over pasting <div> soup into prose.

Here is where it starts to feel alive. Hugo can read structured data from data/ files and loop over it at build time. Drop a data/team.toml in place and you can generate a whole team page from it:

{{ range .Site.Data.team.members }}
  <article class="member">
    <h3>{{ .name }}</h3>
    <p>{{ .role }}</p>
  </article>
{{ end }}

Edit the TOML, rebuild, and the page updates. From the visitor’s side it looks data-driven; in reality it is HTML baked at build time. The same pattern powers related-posts sections (loop over pages sharing a tag), automatic tables of contents (Hugo exposes .TableOfContents for free), and faceted listings. None of it costs the browser anything, because all the work already happened.

For the genuinely interactive bits — search, comments — you still need a little client-side help, but Hugo’s job is to generate the index or the markup those tools consume, and it does that well.

If your site is five pages and never changes, no. Reaching for partials and shortcodes there is over-engineering a problem you do not have, and you should go and do something more fun.

But the moment a site has structure that repeats — many posts, multiple authors, recurring content patterns — this machinery stops being optional and starts being the thing that keeps the site maintainable. A site I run lives or dies by exactly these techniques: one place to change the post layout, shortcodes that keep the Markdown clean, data files that drive listings. The payoff is that adding the hundredth article is no harder than adding the first.

The learning curve is real, mostly because Go’s template syntax is terse and its error messages are blunt. But there is a ceiling to how much you need. Learn partials, learn shortcodes, learn to loop over a data file, and you have covered ninety per cent of what makes a static site feel dynamic. The remaining ten per cent you can look up the day you need it — which, pleasingly, is the same advice the tool’s own philosophy keeps quietly giving you.

Advertisement
Advertisement
Smarc
Written by Smarc

Founder and editor of vo.rs. A lifelong tinkerer who self-hosts far more than is sensible, hardens Linux boxes for fun, and prods the latest AI tools to see what they can really do. The how-to guides here are the notes Smarc wishes had existed the first time round.