Logo

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.

Metadata in Hugo: Boosting SEO and User Experience

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 the author parameter in the site's configuration. You can set this in the config.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.

Latest Posts

Stay updated with our latest insights and news

our experience

10+ Years

We build websites and web apps that work great and look even better. Our team has been helping businesses grow by creating digital solutions that their customers love to use.

"The guys at Erio delivered a stellar project with top-notch code expertise, professionalism, and without a single bug! 😊"
Samantha·Engineer at Bloom Credit
"Erio Software delivered outstanding website development work with impeccable attention to detail. Working with them was a pleasure, thanks to their deep understanding, quick responsiveness, and polite demeanor."
Alejandro·CEO at Caja Negra
"Very patient and supportive team to work with. Would highly recommend."
Mark·Frankly Marketing Agency

Get in touch

Tell us about yourself and the project.