14.7 Common Partials: head.html, header.html, footer.html, SEO meta tags
Right, let’s talk about the workhorses of your template directory. These are the files you’ll include on nearly every page, the ones that handle the repetitive, soul-crushing boilerplate so you don’t have to. We’re going to make them smart, then stitch them together into something that doesn’t suck.
The head.html Partial: Your Page’s First Impression
This little guy is arguably the most important. It’s not seen by your users, but it’s read very carefully by browsers and search engines. A messy head is like showing up to a job interview with your shirt on inside-out. Let’s get it right.
The core of it is your <title> tag. This is non-negotiable. You must pass this in from your page template, because every page is different. Trying to generate it programmatically in the partial is a one-way ticket to SEO hell.
<!-- partials/head.html -->
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ .Title }} | My Brilliant Site</title>
<!-- ... -->
</head>
See that? We’re using a variable .Title. This means every template that calls this partial must define what that title is. It’s a contract. Which brings us to the next point: the viewport meta tag. If you forget this, your beautifully responsive site will look like a shrunken, un-tappable nightmare on mobile. Just copy it. Don’t think about it. It’s a ritual.
SEO Meta Tags: The Optional (But Highly Recommended) Stuff
Now for the “nice-to-haves” that are rapidly becoming “must-haves.” Open Graph tags (for Facebook, LinkedIn, etc.) and Twitter Cards. They make your links look pretty when shared. The absurd part? We have to define these things twice because for some reason, the world’s largest tech companies couldn’t agree on a standard. Sigh.
The smart way is to set sensible defaults in your partial but allow them to be overridden by the page’s front matter. This is where Hugo’s conditional logic shines.
<!-- Inside your head.html partial -->
<meta name="description" content="{{ .Description | default "The default description for my brilliant site." }}">
<!-- Open Graph -->
<meta property="og:title" content="{{ .Title }}" />
<meta property="og:description" content="{{ .Description | default "The default description for my brilliant site." }}" />
<meta property="og:type" content="website" />
<meta property="og:url" content="{{ .Permalink }}" />
{{ with .Params.image }}<meta property="og:image" content="{{ . | absURL }}" />{{ end }}
<!-- Twitter Cards (because of course we need a separate system) -->
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="{{ .Title }}">
<meta name="twitter:description" content="{{ .Description | default "The default description for my brilliant site." }}">
{{ with .Params.image }}<meta name="twitter:image" content="{{ . | absURL }}">{{ end }}
Notice the with block for the image? That’s your best practice right there. It says “if an image is defined in the page’s front matter, render this tag. If not, skip it entirely.” This prevents empty og:image attributes from cluttering up your HTML and potentially causing errors.
The header.html and footer.html Partials: The Bookends
These contain the actual visible stuff: your site logo, navigation, copyright notice, etc. The key insight here is that they often need to be context-aware. Your navigation shouldn’t look the same on the homepage as it does on a blog post.
A common pitfall is hardcoding the “active” state of a navigation link. You see this all the time: someone writes <a href="/about/" class="active">About</a> and that class is there forever, on every page. Don’t do that. Use Hugo’s wonderfully simple conditional check to see if the current page is in that section.
<!-- partials/header.html -->
<header>
<a href="/" class="logo">My Site</a>
<nav>
<a href="/" {{ if .IsHome }}class="active"{{ end }}>Home</a>
<a href="/blog/" {{ if eq .Section "blog" }}class="active"{{ end }}>Blog</a>
<a href="/about/" {{ if eq .RelPermalink "/about/" }}class="active"{{ end }}>About</a>
</nav>
</header>
The footer is usually simpler, often just static content. But even here, think dynamically. That copyright date shouldn’t be hardcoded to 2015.
<!-- partials/footer.html -->
<footer>
<p>© {{ now.Year }} Your Name. All rights reserved, some left.</p>
</footer>
now.Year is Hugo’s way of saying “the current year.” It’s a tiny thing, but it prevents you from looking like your site’s been abandoned for a decade.
Putting It All Together: The Base Template
Now, how do you use these? You create a baseof.html template that acts as the skeleton for every page.
<!-- layouts/_default/baseof.html -->
<!DOCTYPE html>
<html lang="en">
{{- partial "head.html" . -}}
<body>
{{- partial "header.html" . -}}
<main id="content">
{{- block "main" . -}}{{- end -}}
</main>
{{- partial "footer.html" . -}}
</body>
</html>
The magic is in the {{- block "main" . -}} line. This defines a placeholder. Your individual page templates (like single.html or list.html) will now only need to define what goes inside that main block. Hugo automatically wraps it in the full HTML structure, head, header, footer and all. You write the content once, and it appears everywhere, consistently. That’s the power of breaking things into partials. You’re not repeating yourself; you’re building a system.