Ronalds Vilciņš

Cache Repeated Hugo Template Work With partialCached

Hugo already builds most sites quickly, so caching every partial is unlikely to improve anything you can feel. It can also produce the wrong HTML when a cached partial depends on the current page.

partialCached is useful for a narrower problem: the same expensive partial produces the same output many times during one build.

A normal partial runs for every call

Go HTML template
{{ partial "header.html" . }}

Hugo renders that partial each time it is called. For a small header or icon, that is simple and usually fast enough.

The cached form looks similar:

Go HTML template
{{ partialCached "footer.html" . }}

Hugo can reuse the rendered result rather than repeating the work. The important question is not whether the partial can be cached. It is whether every call is allowed to receive the same output.

Site-wide output is the easy case

A social-links partial that reads one site configuration and renders the same list on every page is a reasonable candidate:

Go HTML template
{{ partialCached "social-links.html" . }}

A breadcrumb partial is different. Its output depends on the page:

Go HTML template
{{ partial "breadcrumbs.html" . }}

Caching it without another key may reuse one page’s breadcrumb on another page:

Go HTML template
{{ partialCached "breadcrumbs.html" . }}

That is a fast build producing confidently wrong navigation. Not much of an optimization.

Variants make the cache page-aware

Extra arguments become cache variants. A path can separate breadcrumb output by page:

Go HTML template
{{ partialCached
  "breadcrumbs.html"
  .
  .RelPermalink
}}

For navigation that changes by language, use the language as the variant:

Go HTML template
{{ partialCached
  "navigation.html"
  .
  .Language.Lang
}}

The same idea works for a component type:

Go HTML template
{{ partialCached
  "card.html"
  .
  .Params.type
}}

Every value that can change the rendered HTML must be represented in the cache variants. If the key grows into this:

Go HTML template
{{ partialCached "thing.html" . .Title .Date .Type }}

I would stop and ask whether the saved build time is worth the extra reasoning.

Cache the work that appears in a profile

The best candidates tend to loop through large page collections, sort them, read data files, or build a site-wide index. A partial that prints one SVG is probably too cheap to matter.

Run a normal build first:

Bash
hugo

If the site finishes in 100 milliseconds, keep the normal partial. On a site with thousands of pages, profile the repeated work and cache the specific expensive output.

My default remains partial. I move to partialCached only when the output identity is clear and the build has repeated work worth removing. That keeps cache keys rare enough that their meaning stays obvious.

Filed under and on

← Back to all articles