Metadata in Hugo: Boosting SEO and User Experience
Learn how to leverage Hugo's metadata capabilities to enhance your site's SEO performance and create better user experiences. This guide covers essential metadata tags, best practices for implementation, and advanced techniques for optimizing your Hugo website.
Sat Dec 07 2024 (15 days ago)
Introduction
Metadata is a crucial part of any website, as it helps search engines understand the content of your site and improve your SEO performance. In Hugo, this process is a bit tricky to setup, but once you get the hang of it, it runs smoothly and you can implement it for all of your sites.
Prerequisite: Baseof
To start, modify your baseof.html
file to include the head
partial. This partial will be responsible for rendering the metadata tags in the head of your site.
Let's start small. Here's a basic example of what your baseof.html
file should look like:
<!doctype html>
<html lang="en-us">
{{ partial "head/head" . }}
<body>
<div class="content">
{{ block "main" . }}
{{ end }}
</div>
{{ partial "scripts" . }}
{{ partialCached "footer" . }}
</body>
</html>
The head partial looks like that because I added a head
folder in the partials
folder and put the head.html
file inside it. We'll be adding more files to that folder.
Main head file
Now, let's dive into the head.html
file. This is where things get interesting.
<head>
<meta charset="utf-8" />
<meta http-equiv="x-ua-compatible" content="ie=edge" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no" />
{{ with site.Params.author }}
<meta name="author" content="{{ . }}" />
{{ end }}
{{ hugo.Generator }}
{{ block "head/google-tag-manager" . }}
{{ partial "head/google-tag-manager.html" . }}
{{ end }}
{{ block "head/style" . }}
{{ partial "head/style.html" . }}
{{ end }}
{{ block "head/seo" . }}
{{ partial "head/seo.html" . }}
{{ end }}
</head>
Here's a quick explanation of what each part does:
meta charset="utf-8"
: This tag sets the character encoding of the document to UTF-8, which is the standard encoding for HTML5.meta http-equiv="x-ua-compatible" content="ie=edge"
: This tag sets the compatibility mode for the browser to Edge, which is the latest version of Microsoft Edge.meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"
: This tag configures the viewport to ensure proper scaling on different devices.{{ with site.Params.author }} <meta name="author" content="{{ . }}" /> {{ end }}
: This tag sets the author of the document based on theauthor
parameter in the site's configuration. You can set this in theconfig.toml
file or in a_default/params.toml
file.{{ hugo.Generator }}
: This tag outputs the Hugo version used to generate the document.
We'll talk about the partials in the further sections. Just know that they are between the {{ block }}
tags to render them only if they exist.
Google Tag Manager
Next, let's add the Google Tag Manager partial. This is a snippet of code that you can add to your site to track events and improve your site's performance. We need to do a bit of configuration for this one:
In the config.toml
file, add the following:
[params]
googleTagManagerId = "GTM-XXXXXXX" # Your Google Tag Manager ID
Now, let's add the Google Tag Manager partial. Create a google-tag-manager.html
file in the head
folder and add the following code:
{{ if site.Params.googleTagManagerId }}
<!-- Google Tag Manager -->
<script>
(function (w, d, s, l, i) {
w[l] = w[l] || [];
w[l].push({ "gtm.start": new Date().getTime(), event: "gtm.js" });
var f = d.getElementsByTagName(s)[0],
j = d.createElement(s),
dl = l != "dataLayer" ? "&l=" + l : "";
j.async = true;
j.src = "https://www.googletagmanager.com/gtm.js?id=" + i + dl;
f.parentNode.insertBefore(j, f);
})(
window,
document,
"script",
"dataLayer",
"{{ site.Params.googleTagManagerId }}"
);
</script>
<!-- End Google Tag Manager -->
{{ end }}
This file was already referenced in the head.html
file.
Finally, we also need to add the noscript tag to the body of the page as a fallback for users who have JavaScript disabled. Let's add it to the baseof.html
file:
<!doctype html>
<html lang="en-us">
{{ partial "head/head" . }}
<body>
{{ if site.Params.googleTagManagerId }}
<!-- Google Tag Manager (noscript) -->
<noscript>
<iframe
src="https://www.googletagmanager.com/ns.html?id={{ site.Params.googleTagManagerId }}"
height="0"
width="0"
style="display:none;visibility:hidden"></iframe>
</noscript>
{{ end }}
<div class="content">
{{ block "main" . }}
{{ end }}
</div>
{{ partial "scripts" . }}
{{ partialCached "footer" . }}
</body>
</html>
SEO
Let's skip the style partial for now since it's not relevant for this post and continue with the SEO partial. This is where we'll add the metadata tags for the page.
Create a seo.html
file in the head
folder and add the following code:
<meta name="robots" content="index, follow" />
<meta
name="googlebot"
content="index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1"
/>
<meta
name="bingbot"
content="index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1"
/>
{{ if .IsHome -}}
<title>
{{ site.Params.title }}
</title>
{{ else -}}
<title>
{{ .Title }} {{ .Site.Params.titleSeparator }} {{ .Site.Params.title }}
</title>
{{ end -}}
{{ with .Description -}}
<meta name="description" content="{{ . }}" />
{{ else -}}
{{ with .Summary | plainify -}}
<meta name="description" content="{{ . }}" />
{{ else -}}
<meta name="description" content="{{ .Site.Params.description }}" />
{{ end -}}
{{ end -}}
<link rel="canonical" href="{{ .Permalink }}" />
{{ partial "head/opengraph.html" . }}
{{ partial "head/twitter_cards.html" . }}
Big one. Let's break it down:
meta name="robots" content="index, follow"
: This tag sets the robots meta tag to index and follow, which means that search engines should index and follow the links on the page.meta name="googlebot" content="index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1"
: This tag sets the Googlebot meta tag to index and follow, with a maximum snippet length of -1 (no snippets), a maximum image preview size of large, and a maximum video preview length of -1 (no videos).meta name="bingbot" content="index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1"
: This tag sets the Bingbot meta tag to index and follow, with a maximum snippet length of -1 (no snippets), a maximum image preview size of large, and a maximum video preview length of -1 (no videos).- The title part sets the title of the page based on the
title
parameter in the site's configuration. If the page is the home page, it will use the site's title. Otherwise, it will use the page's title, followed by the site's title separator, and then the site's title. - The description part sets the description of the page based on the
.Description
variable. If the.Description
variable is not set, it will use the page's summary, or the site's description if neither is set. - The canonical link tag sets the canonical URL of the page to the page's permalink.
Open Graph
Open Graph is a protocol that allows you to control how your content is displayed when it's shared on social media. This is what Facebook and other social media platforms use to display your content when someone shares it.
Create a opengraph.html
file in the head
folder and add the following code:
<meta property="og:type" content="{{ if .IsPage }}article{{ else }}website{{ end }}">
<meta property="og:title" content="{{ .Title }}">
<meta property="og:description" content="{{ with .Description }}{{ . }}{{ else }}{{if .IsPage}}{{ .Summary }}{{ else }}{{ with .Site.Params.description }}{{ . }}{{ end }}{{ end }}{{ end }}">
<meta property="og:url" content="{{ .Permalink }}">
{{ with .Site.Params.title -}}
<meta property="og:site_name" content="{{ . }}">
{{ end -}}
{{ $iso8601 := "2006-01-02T15:04:05-07:00" -}}
{{ if .IsPage -}}
{{ if not .PublishDate.IsZero -}}
<meta property="article:published_time" {{ .PublishDate.Format $iso8601 | printf "content=%q" | safeHTMLAttr }}>
{{ else if not .Date.IsZero -}}
<meta property="article:published_time" {{ .Date.Format $iso8601 | printf "content=%q" | safeHTMLAttr }}>
{{ end -}}
{{ if not .Lastmod.IsZero -}}
<meta property="article:modified_time" {{ .Lastmod.Format $iso8601 | printf "content=%q" | safeHTMLAttr }}>
{{ end -}}
{{ else -}}
{{ if not .Date.IsZero -}}
<meta property="og:updated_time" {{ .Lastmod.Format $iso8601 | printf "content=%q" | safeHTMLAttr }}>
{{ end -}}
{{ end -}}
{{ with .Params.image }}
<meta property="og:image" content="{{ $.Permalink }}{{ . }}">
{{ end }}
{{ with .Params.audio -}}
<meta property="og:audio" content="{{ . | absURL }}">
{{ end -}}
{{ with .Params.videos -}}
{{ range . -}}
<meta property="og:video" content="{{ . | absURL }}">
{{ end -}}
{{ end -}}
For this part, we're just setting the Open Graph metadata for the page using Hugo variables. We do some conditional checks to make sure we're getting the right values depending on the page type and what's available.
Twitter Cards
This is the same as the Open Graph protocol, but for Twitter/X.
Create a twitter_cards.html
file in the head
folder and add the following code:
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:site" content="{{ .Site.Params.twitterSite }}">
<meta name="twitter:creator" content="{{ .Site.Params.twitterCreator }}">
<meta name="twitter:title" content="{{ .Title }}">
<meta name="twitter:description" content="{{ .Description }}">
{{ with .Params.image }}
<meta name="twitter:image" content="{{ $.Permalink }}{{ . }}">
{{ else }}
{{ with $.Params.images -}}
<meta name="twitter:image" content="{{ $.Permalink }}{{ index . 0 }}">
{{ else -}}
{{ $images := $.Resources.ByType "image" -}}
{{ $featured := $images.GetMatch "*feature*" -}}
{{ if not $featured -}}
{{ $featured = $images.GetMatch "{*cover*,*thumbnail*}" -}}
{{ end -}}
{{ with $featured -}}
<meta name="twitter:image" content="{{ $featured.Permalink }}">
{{ else -}}
{{ with $.Site.Params.images -}}
<meta name="twitter:image" content="{{ index . 0 | absURL }}">
{{ else -}}
<meta name="twitter:card" content="summary">
{{ end -}}
{{ end -}}
{{ end -}}
{{ end -}}
<meta name="twitter:image:alt" content="{{ .Title }}">
Same here, we're just setting the Twitter Cards metadata for the page using Hugo variables.
Conclusion
That's it! You've now added metadata to your Hugo site. This should help with your SEO performance and create a better user experience. There's a lot more to metadata than what we've covered here, like implementing schema.org, but this should give you a good starting point.