36.6 How partialCached Works Internally
Right, let’s pull back the curtain on partialCached. You’re probably using it because you heard it’s a “performance win,” and it is, but you need to understand its particular brand of magic to avoid its particular brand of heartbreak. Think of it not as a smarter partial, but as a slightly lazy, forgetful, but very efficient clone of your partial.
Its core purpose is brutally simple: avoid re-rendering the same template with the same input data more than once during a single site build. The key words there are “same input” and “single site build.” This isn’t a persistent cache between builds; it’s a short-term memory for the duration of the hugo command you just ran.
The Gory Internals: A Map, A Lock, and A String
So, how does this lazy clone actually work? Under the hood, Hugo creates a global cache—a big map[string]interface{}—that is shared across all your partialCached calls for this build. The real magic is in the key it uses to store and retrieve your rendered template.
When you call {{ partialCached "my-list.html" . }}, the default cache key is generated from:
- The partial’s name (
"my-list.html"). - The string representation of the context you passed in (
.).
And there’s the first potential foot-gun. The context (.) is converted to a string to form the key. For a page variable, this often becomes its .RelPermalink. For a taxonomy term, it might be the term name. This is fast and works… until it doesn’t.
Let’s say you have a partial that takes a page object and renders its title and date. You might cache it like this:
{{ partialCached "article-header.html" . }}
For most pages, this is fine. The key becomes something like "article-header.html:/posts/my-cool-post/". But what if you pass a custom data structure?
{{ $myData := dict "Title" "My Title" "Tags" (slice "hugo" "go") }}
{{ partialCached "data-thing.html" $myData }}
The key here will be the string representation of that dictionary, which is a meaningless "map[Title:My Title Tags:[hugo go]]". The moment any value in that map changes, even slightly, the string changes, and you get a new cache entry. This is rarely what you want and can lead to the cache being useless or even causing memory bloat.
Taking Control: The Manual Cache Key
This is why the smartest way to use partialCached is almost always to provide your own, explicit, human-readable cache key using the optional third parameter.
{{ partialCached "article-header.html" . .RelPermalink }}
{{ partialCached "data-thing.html" $myData (printf "data-thing-%s" .Title) }}
{{ partialCached "tag-list.html" . (print .Kind "-" .Title) }}
Now you are in control. You’re telling Hugo, “Use this specific string as the key.” This solves the unpredictable stringification problem and allows for incredibly powerful patterns. You can cache a partial across different pages as long as the key is the same.
Imagine a partial that renders a “Recent Posts” widget. The data is the same on every page that calls it! You don’t want to re-render it for every single page. You just want to render it once and reuse the HTML everywhere.
{{ partialCached "recent-posts.html" site.RegularPages "recent-posts-homepage" }}
Boom. It renders once at the first invocation, and every subsequent call for the rest of the build just pulls the pre-rendered HTML from the cache using the "recent-posts-homepage" key. This is where you see massive build performance gains.
The Gotchas and The Glory
Vary by Language: If your site is multilingual, you must vary your cache key by language, or you’ll serve the wrong language’s content.
{{ partialCached "greeting.html" . (print .RelPermalink .Lang) }}is a common pattern.It’s Not a Panacea:
partialCachedonly caches the final HTML output. It doesn’t magically make the logic inside your partial faster. If your partial does heavy data processing (like sorting a giant list of pages), that still happens on every cache miss. You should combinepartialCachedwith caching the data itself using Hugo’scacheresource in your Go templates.The Golden Rule: If the output of a partial is truly idempotent—meaning it will be identical for a given set of inputs every single time—
partialCachedis your best friend. If the output might change based on something outside its inputs (e.g., what page it’s currently on, a counter, a random number), then for the love of God, do not cache it. You’ll get inconsistent, maddening results.
Use it wisely, with explicit keys, and watch your build times plummet. Use it carelessly, and you’ll be staring at a website wondering why half the content is wrong.