14.1 The layouts/partials/ Directory
Right, let’s talk about the layouts/partials/ directory. This isn’t just another folder Hugo tells you to use; this is your new best friend, your toolbox, your secret weapon against the soul-crushing dread of repeated code. Think of partials as the reusable fragments of your site’s UI. That header you copy-paste into every single layout? That’s a partial. The footer that’s identical on every page? Partial. That complex “related articles” component you spent three days building? For the love of all that is holy, make it a partial.
Hugo is smart, but it’s not psychic. It knows to look in this specific directory—layouts/partials/—when you call the partial function in a template. This is a convention, and a damn good one. It keeps things organized and predictable. You don’t have to register them or import them anywhere; just toss a file in there and you can call it from any other template in your project.
The Anatomy of a Partial Call
Using a partial is dead simple. You use the partial function, which takes two arguments: the path to the partial (without the file extension) and a “context” you want to send to it. This “context” is powerful—it’s how you pass data into your reusable fragment.
{{/* In your baseof.html or a single template */}}
<head>
<title>My Site</title>
{{ partial "head/meta" . }}
</head>
<body>
{{ partial "header" . }}
<main>
{{ block "main" . }}{{ end }}
</main>
{{ partial "footer" . }}
{{ partial "scripts.html" . }}
</body>
Notice I used two slightly different names: "header" and "scripts.html". Hugo is forgiving. It will look for header.html in the partials directory. If you specify the extension, it still works. I tend to omit it because it’s cleaner, but it’s a matter of taste.
Passing Data (And Why . Is Your Default)
The second argument to the partial function is crucial. That dot (.) is the current context. When you pass the dot from your list template, the partial receives the page data for a single item in that list. When you pass it from a single page template, it gets all the data for that page.
But sometimes you don’t want to pass the entire context. Maybe you just want to send one specific value. This is a best practice that makes your partials more predictable and less coupled to a specific page structure.
{{/* Instead of this, which forces the partial to dig into .Params */}}
{{ partial "author-bio" . }}
{{/* Do this. Pass only what the partial needs. */}}
{{ partial "author-bio" (dict "name" .Params.author_name "bio" .Params.author_bio) }}
Now, inside author-bio.html, the context isn’t the whole page; it’s a dictionary with just name and bio keys. This is infinitely better. The partial is now a true independent component. You could use it anywhere on your site, even outside of the usual page context, just by passing it a name and a bio. This is the difference between a janky script and professional, reusable code.
The Scoping Trap (And How to Avoid It)
Here’s a classic “I just wasted an hour” pitfall. Inside a partial, the scope changes. The dot becomes whatever you passed as the second argument. This means all the parent scope’s variables—like $site or .Title—are now inaccessible unless you explicitly passed them.
If you need access to something from the global site object (site)inside a partial that only received a page’s title, you have to reach for the global site variable. It’s a bit clunky, but it works.
{{/* Inside a partial that was passed a 'title' string */}}
<h2>{{ .title }}</h2>
<p>See more on our <a href="{{ site.BaseURL }}">homepage</a></p> {{/* site is still available */}}
Organizing Your Partials (Because Chaos Isn’t Cute)
Just throwing 50 files into the root of partials/ is a recipe for frustration. Hugo lets you use subdirectories, and you absolutely should. Organize them by function.
layouts/partials/
├── head/
│ ├── meta.html
│ ├── styles.html
│ └── analytics.html
├── header.html
├── footer.html
├── components/
│ ├── card.html
│ ├── newsletter-form.html
│ └── author-bio.html
└── utils/
├── icon.html
└── debug-dump.html
To call a partial in a subdirectory, use a slash: {{ partial "head/meta" . }} or {{ partial "components/card" . }}. This keeps everything sane and findable. The utils/ directory is a pro tip—stick tiny, helper partials in there, like one that uses printf to generate an SVG icon based on a name you pass it. You’ll use it everywhere.