Updates in June 2021

Cover image: Star vector created by upklyak - www.freepik.com

I have continued learning about Hugo capabilities in June and have implemented several small improvements:

  1. added image attribution and
  2. parallax effect in blog posts;
  3. customized title for a feedback form;
  4. added robots.txt;
  5. fixed sitemap.xml;
  6. added meta for social pages sharing.

Image attribution

Hugo has a nice set of default front matter variables, including images. I was thinking how this can be used and came up with a two-image approach: first image in a blog post works as cover picture and second image is used for a parallax effect:

  - june_updates_small.jpg| Star vector created by upklyak - www.freepik.com
  - june_updates.jpg| Star vector created by upklyak - www.freepik.com

Important thing is that I don’t own these pictures and most of them require some kind of attribution. My decision was to add a pipe separator after an image file name and mention the image author on the same line. Later on I could check the whole line, retrieve the image name and attribution text in a Hugo layout. For example, take a look at a Summary.html layout that is used to list blog posts:

<div class="card">
  <div class="postsummary">
    {{ if isset .Params "images" }}
      {{$img_options := (split (index .Params.Images 0) "|")}}
    <a href="{{ .Permalink }}">
      <img class="card-img-top" src="{{.Permalink}}{{trim (index $img_options 0) " "}}"
                                alt="{{ .Title }}" title="{{trim (index $img_options 1) " "}}" >
    {{ end }}
    <div class="card-body">
      <h5 class="card-title"><a href="{{ .Permalink }}">{{ .Title }}</a></h5>
      <p class="card-text">{{ .Summary }}</p>
      <p class="card-text">
        <small class="text-muted">
          <i class="fas fa-calendar-alt"></i> {{ .Date.Format "2006-01-02" }}
{{ if gt .ReadingTime 1 }} {{ .Scratch.Set "readingTime" "mins" }} {{ else }} {{ .Scratch.Set "readingTime" "min" }} {{ end }} &#183; {{ .ReadingTime }} {{ .Scratch.Get "readingTime" }}

If a blog post has any images at all, the first line is taken and split into two parts {{$img_options := (split (index .Params.Images 0) “|”)}}. Later on the first element {{ trim (index $img_options 0) " " }} is used as a file URL and the second one {{ trim (index $img_options 1) " " }} - as an attribution text.

Parallax effect

Parallax effect works in a similar way, but with some additions. At first, it is not taken for granted that a blog post front matter images variable has two items. That’s why an additional check is needed in the article layout:

    {{ if isset .Params "images" }}
      {{$img_options := (split (index .Params.Images 1) "|")}}
      {{if gt (len (index $img_options 0)) 0}}
        <script src="/js/parallax.js"></script>
              <div class="parallax" data-type="background" style="margin-bottom:20px;">
            <img src="{{.Permalink}}{{trim (index $img_options 0) " "}}" class="parallax__img">
            <div class="parallax__text-box">
              <h1 class="parallax__title">{{ .Title }}</h1>
              <span class="parallax__subtitle"><time>Published on {{ .Date.Format "2006-01-02" }}</time></span>
              {{if gt (len (index $img_options 1)) 0}}
              <span class="parallax__subtitle" style="font-style: italic;">Cover image: {{trim (index $img_options 1) " "}}</span>{{ end }}
          <div class="container" style="margin-bottom:20px;">
                <h1>{{ .Title }}</h1>
                <time>Published on {{ .Date.Format "2006-01-02" }}</time>
      {{ end }}
    {{ end }}

The line {{if gt (len (index $img_options 0)) 0}} checks whether a second item of the images settings has something at all. If yes, then we can produce a blog post header with a parallax effect. If not - then a standard header is enough. Second, small parallax.js script should be added to the page: <script src=“/js/parallax.js”></script>. Original HTML/CSS + JS example can be found in this Codepen snippet. The example is very simple and doesn’t allow multiple images with parallax effect on the same page. But for me it does the job very well.

Page-specific feedback form title

Another thing I wanted to improve was a feedback form: it would be great to see a website page title where the user has filled the form. By default, formspree.io uses a static email subject, which can be easily extended via Page title option:

<input type="hidden" name="_subject" id="email-subject"
value="stats-consult form submitted from page: {{ if .Page.Title }}{{ .Page.Title }}{{ else }}{{ .Site.Title }}{{ end }}">

SEO tweaks

So far, the website contains virtually no SEO elements. Search engines make some default assumptions, but it was a good exercise to remember SEO basics and introduce some important items.


Hugo is capable of generating robots.txt file itself. I used an instruction from Google and added a few steps: excluded Rmd/md files and a few subfolders (as I didn’t implement a proper tags search yet). Also, a link to sitemap.xml was explicitly provided. The result looks like this:

# check the robots.txt instructions here https://developers.google.com/search/docs/advanced/robots/create-robots-txt#syntax
User-agent: *
Allow: /
Disallow: /*.md$
Disallow: /*.Rmd$
Disallow: /categories/
Disallow: /tags/

Sitemap: https://stats-consult.com/sitemap.xml


Sitemap.xml file should follow certain strict rules. For example, URLs for all pages should be explicit. Hugo can create a default sitemap, but it was a bit too simplistic. I have added a few additional things: possibility to exclude certain pages (this can be done via page front matter settings) and slightly tweaked page URLs to make sure they provide full paths to each page. The resulting layout is here:

{{ printf "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>" | safeHTML }}
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
  {{ range .Data.Pages }}{{ if ne .Params.sitemap_ignore true }}
    <loc>{{ .Permalink }}</loc>{{ if not .Lastmod.IsZero }}
    <lastmod>{{ safeHTML ( .Lastmod.Format "2006-01-02T15:04:05-07:00" ) }}</lastmod>{{ end }}{{ with .Sitemap.ChangeFreq }}
    <changefreq>{{ . }}</changefreq>{{ end }}{{ if ge .Sitemap.Priority 0.0 }}
    <priority>{{ .Sitemap.Priority }}</priority>{{ end }}{{ if .IsTranslated }}{{ range .Translations }}
                hreflang="{{ .Language.Lang }}"
                href="{{ .Permalink }}"
                />{{ end }}
                hreflang="{{ .Language.Lang }}"
                href="{{ .Permalink }}"
                />{{ end }}
  {{ end }}{{ end }}

page meta for social pages

I was curious how do social networks take nice pictures for the articles when someone puts a link to an external website. Turned out that most platforms are not too smart and rely on the Open Graph meta tags.

Luckily, Hugo provides two internal templates for generating such meta tags:

  • _internal/opengraph.html
  • _internal/twitter_cards.html

These templates didn’t work out-of-the-box for me due to image attribution enhancements. In order to make Open Graph templates work, I first copied the most recent opengraph.html and twitter_cards.html templates into the theme’s *layout/_default* folder.

Next, two small fixes were done:

  1. Site’s default image (coming from $.Site.Params.images) is always mentioned in the Open Graph meta tags.
  2. Article’s image is processed a bit differently. Article URL is added and the image name is extracted from the pipe-separated line:
{{$imgurl := .Permalink}}
{{- with $.Params.images -}}
{{- range first 1 . }}<meta property="og:image" content="{{$imgurl}}{{trim (index (split (.) "|") 0) " "}}" />{{ end -}}
{{- else -}}


There are many other ways how a website can be improved with Hugo (check a TODO list post), but for now I am satisfied with the current results, it was interesting and challenging to dig into a new CMS framework. I wouldn’t probably encourage everyone to go the same route, but it was definitely an exciting task.

Here are some other posts:

A truly static blogdown website
A truly static blogdown website

How to create and deploy a custom static Hugo site with blogdown R package.

2021-05-30 · 8 mins

Moving to Hugo
Moving to Hugo

Finally got some free time to learn blogdown package. Here is a story of converting my website from Wordpress to Hugo.

2021-05-29 · 2 mins

Multilanguage Shiny App - Advanced Cases
Multilanguage Shiny App - Advanced Cases

My approach to translating Shiny apps via shiny.i18n package.

2021-03-15 · 5 mins