Preface

Goal: Flexible SEO with Hugo, w3c, opengraph and twitter.

Don’t you love a preview on your site? I mean, that you can customize differently, based on the content.

Hugo Meta: Opengraph

You can see at above figure, each post has different image.


1: Prepare

There are few artefacts changes.

Artefacts

Edit

  • config.toml.

  • themes/tutor-06/layouts/partials/site-head.html.

New

  • themes/tutor-06/layouts/partials/meta-opengraph.html.

  • themes/tutor-06/layouts/partials/meta-twitter.html.

Example Frontmatter

  • content/quotes/john-mayer-slow-dancing-in-a-burning-room.md.

2: Refactoring: Head Tag

Our opengraph meta, lies inside head tag.

Layout: Head

To avoid complicated code, we are going to us partial for opengraph. Since we are going to use partial for opengraph, we should have a look at our last site-head, and add this two lines.

<head>
    {{ partial "meta-html.html" . }}
    ...
    {{ partial "meta-seo.html" . }}
    {{ partial "meta-opengraph.html" . }}
    {{ partial "meta-twitter.html" . }}
</head>

Layout: Enhanced Head

For a more complete code, I also provice an enhanced version of site-head. My intention is to make a more complete example for you. I also put some comment, so we know what each part does.

  • themes/tutor-06/layouts/partials/site-head.html.
<head>
    {{ partial "meta-html.html" . }}

  {{/* NOTE: the Site's title, and if there is a page title, that is set too */}}
    <title>{{ .Page.Title | default .Site.Title }}</title>

  {{/*  Bootstrap CSS */}}
    <link rel="stylesheet" type="text/css" href="{{ "css/bootstrap.css" | relURL }}">
    <link rel="stylesheet" type="text/css" href="{{ "css/main.css" | relURL }}">
    <link rel="stylesheet" type="text/css" href="{{ "css/prism.css" | relURL }}">
    {{ block "custom-stylesheet" . }}{{ end }}

  {{/* javascript */}}

  {{/* miscellanous */}}
    <link href="{{ "favicon.ico" | relURL }}" rel="shortcut icon" type="image/x-icon" />

    {{ partial "meta-seo.html" . }}
    {{ partial "meta-opengraph.html" . }}
    {{ partial "meta-twitter.html" . }}

  {{/* unused */}}
</head>

SEO: HTML Preview

Don’t you love that your site is ready for Search Engine Optimization ? The HTML that we want to achieve is similar as example page below:

<head>
...
    <meta name="author"      content="epsi">
    <meta name="description" content="Shiny and sparkly.   And splendidly bright.   Here one day.   Gone one night. ">
    <meta name="keywords"    content="lyric, pop, 90s">

    <meta property="og:locale"       content="en-us">
    <meta property="og:type"         content="article">
    <meta property="og:title"        content="Michael Jackson - Gone Too Soon">
    <meta property="og:description"  content="Shiny and sparkly.   And splendidly bright.   Here one day.   Gone one night. ">
    <meta property="og:url"          content="http://localhost:1313/quotes/2015/05/15/michael-jackson-gone-too-soon/">
    <meta property="og:site_name"    content="Letters to my Beloved">
    <meta property="og:image"        content="http://localhost:1313/images/epsi-vexel.png">

    <meta property="og:latitude"     content="-6.193665"/>
    <meta property="og:longitude"    content="106.848558"/>
    <meta property="og:locality"     content="Jakarta"/>
    <meta property="og:country-name" content="Indonesia"/>

    <meta name="twitter:title"       content="Michael Jackson - Gone Too Soon">
    <meta name="twitter:description" content="Shiny and sparkly.   And splendidly bright.   Here one day.   Gone one night. ">
...
</head>

Hugo Meta: SEO


3: Meta HTML

Consider move important meta to special meta-html.html. This should be an easy task, since it is only HTML.

Layout: Meta HTML

Remember that, some of these metas must come first in the head tag.

  • themes/tutor-06/layouts/partials/meta-html.html.
  {{/* Required meta tags */}}
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
  {{/* The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags */}}

    <meta name="HandheldFriendly" content="True">
    <meta name="MobileOptimized" content="320">

Optionally, you can add any meta that you need.

  {{/* Let's Fake the Generator */}}
    <meta name="generator" content="Karimata 1.2.3 Media Engine" />

  {{/* Chrome, Firefox OS and Opera */}}
    <meta name="theme-color" content="#2980b9">

4: SEO: W3C

The next step is, put valid w3c SEO related meta.

Layout: Meta SEO

  • themes/tutor-06/layouts/partials/meta-seo.html.
  {{- if .Page.Params.Author }}
    <meta name="author"      content="{{ .Page.Params.Author }}">
  {{- end }}

  {{- if .Page.Params.Excerpt }}
    <meta name="description" content="{{ replace .Page.Params.Excerpt "\n" " " }}">
  {{- end }}

  {{- $terms := union .Page.Params.Categories .Page.Params.Tags -}}
  {{- if $terms }}
    <meta name="keywords"    content="{{ delimit $terms ", " }}">
  {{- end }}

Content: Example

If you need, you can experiment with excerpt parameters in frontmatter.

+++
type       = "post"
title      = "Michael Jackson - Gone Too Soon"
date       = 2015-05-15T07:35:05+07:00
categories = ["lyric"]
tags       = ["pop", "90s"]
slug       = "michael-jackson-gone-too-soon"
author     = "epsi"

excerpt    = """\
  Shiny and sparkly.
  And splendidly bright.
  Here one day.
  Gone one night.
"""
+++

Shiny and sparkly.
And splendidly bright.
Here one day.
Gone one night.

This will result as below:

    <meta name="description" content="Shiny and sparkly.   And splendidly bright.   Here one day.   Gone one night. ">

How Does Keywords Works?

Merge array is easy in Hugo, using union:

{{- $terms := union .Page.Params.Categories .Page.Params.Tags -}}

Join the array into one string require delimit function.

{{ delimit $terms ", " }}

This will result as below:

    <meta name="keywords"    content="lyric, pop, 90s">

5: SEO: Opengraph

The Structure of opengraph can be read from official page:

HTML Preview

The HTML that we want to achieve is similar as below.

    ...
    <meta property="og:title" content="John Mayer - Slow Dancing in a Burning Room">
    <meta property="og:url" content="http://localhost:1313/quotes/2018/02/15/john-mayer-slow-dancing-in-a-burning-room/">
    <meta property="og:site_name" content="Letters to my Beloved">

    <meta property="og:image" content="http://localhost:1313/assets/posts/2018/kiddo-007.jpg">
    ...

Structure

How to achieve ?

There are three parts:

  • Common properties: using conditional.

  • Image: using default image, depend on page type.

  • Location: Hardcoded.

Partial: Meta Opengraph

This would takes some conditional, but it self explanatory.

    <meta property="og:locale"       content="{{ $.Site.LanguageCode | default "en" }}">

  {{- if eq .Page.Params.Type "post" }}
    <meta property="og:type"         content="article">
  {{- end }}
    <meta property="og:title"        content="{{ .Title | default .Site.Title }}">

  {{- if .Page.Params.Excerpt }}
    <meta property="og:description"  content="{{ replace .Page.Params.Excerpt "\n" " " }}">
  {{- end }}
    <meta property="og:url"          content="{{ .Permalink }}">
    <meta property="og:site_name"    content="{{ .Site.Title }}">

  {{- if .Page.Params.Opengraph.Image }}
    <meta property="og:image"        content="{{ .Page.Params.Opengraph.Image | absURL }}">
  {{- else if eq .Page.Params.Type "post" }}
    <meta property="og:image"        content="{{ "/images/epsi-vexel.png" | absURL }}">
  {{- else if .Site.Params.Opengraphimage }}
    <meta property="og:image"        content="{{ .Site.Params.Opengraphimage | absURL }}">
  {{- end }}

    <meta property="og:latitude"     content="-6.193665"/>
    <meta property="og:longitude"    content="106.848558"/>
    <meta property="og:locality"     content="Jakarta"/>
    <meta property="og:country-name" content="Indonesia"/>

Default Image

Now we have three possibility

  • General: set in config.toml

  • Post Type: hardcoded as /assets/site/images/epsi-vexel.png

  • Set in frontmatter.

Generic Image

We can set parameters in config.toml, as below:

[params]
  description = """\
    Learn and Discover Open Source with Daily Genuine Experience.
    From Coding, Front End, Back End, Database, and Static Site Generator.
    """

  opengraphimage = "assets/site/images/logo-gear-opengraph.png"

For any pages, except post, we can use the opengraphimage parameter above.

Default Image for Post

Since it is just an example. I apologize for my hardcoded example. You can code whatever you like, rather than just hardcoded example.

Frontmatter Example

And in any post, we can set, as example below:

  • content/quotes/john-mayer-slow-dancing-in-a-burning-room.md.
type       = "post"
title      = "John Mayer - Slow Dancing in a Burning Room"
date       = 2018-02-15T07:35:05+07:00
categories = ["lyric"]
tags       = ["rock", "2010s"]
slug       = "john-mayer-slow-dancing-in-a-burning-room"
author     = "epsi"

[opengraph]
  image    = "assets/posts/2018/kiddo-007.jpg"

6: SEO: Twitter

The twitter version is much more simple. Because, I do not really pay attention to twitter.

    <meta name="twitter:title"       content="{{ .Title | default .Site.Title }}">

  {{- if .Page.Params.Excerpt }}
    <meta name="twitter:description" content="{{ replace .Page.Params.Excerpt "\n" " " }}">
  {{- end }}

  {{- if .Site.Data.owner.twitter }}
    <meta name="twitter:site"        content="@{{ .Site.Data.owner.twitter }}">
  {{- end }}

Summary

I think that is all. Short and simple.


What is Next ?

There are, some interesting topic about using Service in Hugo, such as inserting Google Analytics and Disqus Comments. Consider continue reading [ Hugo - Service ].

Thank you for reading.