Hugo tips and tricks

This is part of the series: Publishing with Hugo

Link to this sectionIntro

Getting right into it, here’s a grab bag of techniques that I use in my Hugo site.

Disclaimer: one thing about Hugo is that there are many ways to do the same thing so I’ll update this post as I find more succinct or “correct” methods. If you know of a better way of doing any of these, please drop me a line . Post post scriptum, at time of writing I’m using Hugo version 0.96.0.

Link to this sectionCompiling SASS

SASS is one of many CSS pre-compiled languages. To get it working in Hugo means placing your SASS file, for example, in themes/<theme-name>/assess/sass/main.scss and then adding this to the head section of your HTML template:

{{ $sass := resources.Get "sass/main.scss" }}
{{ $style := $sass | resources.ToCSS }}
<link rel="stylesheet" href="{{ $style.Permalink }}" integrity="{{ $style.Data.Integrity }}">

Link to this sectionOrdering by published date

I like to specify a published date for posts so that I can have a bunch of posts queued up and publish them over time instead of when they are completed. Hugo supports this out of the box with a publishDate tag in your front matter but your templates need to take this into account to get the ordering to respect that.

Link to this sectionNext or previous post

Having a next and previous link on a post (like I do at the bottom) can be accomplished with this convoluted snippet:

<ul>
{{ $pages := where $.Site.Pages.ByPublishDate.Reverse "Type" "posts" }}
{{ range $key, $page := $pages }}
  {{ if eq $ . }}
    {{ $prev := (index $pages (sub $key 1)) }}
    {{ with $prev }}
  <li><a title="{{ .Title }}" href="{{ .Permalink }}">« newer</a></li>
    {{ end }}
    {{ $next := (index $pages (add $key 1)) }}
    {{ with $next }}
  <li><a title="{{ .Title }}" href="{{ .Permalink }}">older »</a></li>
    {{ end }}
  {{ end }}
{{ end }}
</ul>

Link to this sectionPosts listing

The listing of posts ordered by publish date is a bit simpler and can be done with this kind of template snippet:

<ul>
{{ range (.Paginate .Pages.ByPublishDate.Reverse).Pages }}
  <li>
    <article>
      <h2><a title="{{ .Title }}" href="{{ .Permalink }}">{{ .Title }}</a></h2>
      <div class="date">//{{ .PublishDate.Format "Jan 1, 2006" }}</div>
    </article>
  </li>
{{ end }}
</ul>

Link to this sectionSetting the page description

The page description meta tag is relatively easy to set, but you might want to pull it from different places depending on the page. This is what I have in my baseof.html file:

<meta name="description" content="{{- block "description" . -}}{{- if .IsHome -}}{{- .Summary -}}{{- else -}}{{- with .Description -}}{{- . -}}{{- else -}}{{- if .IsPage -}}{{- .Summary -}}{{- else -}}{{- with .Site.Params.description -}}{{- . -}}{{- end -}}{{- end -}}{{- end -}}{{- end -}}{{- end -}}">

But Michael, wtf does that monstrosity do? Something like:

  • First define a “description” block that can be replaced, inside that:
    • Check if it’s the home page, if so:
      • Use the .Summary
    • Otherwise:
      • Try to use the .Description:
        • Output that here
      • If no .Description:
        • Check if it’s a normal page:
          • If so output the .Summary
        • Otherwise:
          • Try to use the site description parameter

It can be polite to visitors to indicate that a link is external to the current site like this . Furthermore, you probably want those links opening in another window or tab. Hugo let’s you automate that by using a render hook template, which is called every time a link is encountered by the Markdown parser. To get this working you’ll need a render-link.html file in your theme’s layouts/_default/_markup directory containing something like:

<a href="{{ .Destination | safeURL }}" title="{{ .Title }}"{{ if strings.HasPrefix .Destination "http" }} target="_blank"{{ end }}>
  {{- .Text | safeHTML -}}
  {{- if strings.HasPrefix .Destination "http" -}}
    <svg ... />
  {{- end -}}
</a>

Replace the svg tag with an svg icon of your choice. Personally I went with a feather icon .

To get links appearing on headers is similar to external links and is done through a render hook template. This time the template is render-heading.html and is placed in the same directory as the external link template.

Here is the template I am using:

{{ $class := index .Attributes "class" }}
{{ $style := index .Attributes "style" }}
<h{{ .Level }} id="{{ .Anchor | safeURL }}"{{ if ne $class nil }} class="{{ $class }}" {{ end }}{{ if ne $style nil }} style="{{ $style }}" {{ end }}>
  <a class="header-link nohover" href="#{{ .Anchor | safeURL }}">
    <svg ... />
  </a>{{- .Text | safeHTML -}}
</h{{ .Level }}>

Again, same deal regarding the svg part. You may wonder what the $class and $style items are. To use those involves specifying a special attributes section after a heading such as:

## Heading
{style="color: red" .special}

… and without that in the above template those attributes won’t get placed in.

Link to this sectionTable of contents

To render a table of contents at the top of the post you’ll need to do the following:

  1. Configure the [markup.tableOfContents] section in your config.toml (the defaults will do)
  2. Add in front matter param such as toc which you then set to true or false depending on whether it should show for the post
  3. In your post template add a snippet such as:
  {{ if .Params.toc }}
  <section id="toc">
  {{ .TableOfContents }}
  </section>
  {{ end  }}

Link to this sectionPost templates

When creating new posts or pages you most likely want consistent front matter tags already specified. To do that just drop in a markdown file with the name of the type in the archetypes directory. For example, this is what I have in my archetypes/posts.md file:

---
title: "{{ replace .Name "-" " " | title }}"
summary:
date: {{ .Date }}
publishDate:
tags: []
series:
toc: false
draft: true
---

After that, creating a post that uses this template is as simple as mashing in hugo new posts/your-new-post into your terminal.

Link to this sectionLast modified date

By default the last modified date will pull from the lastmod front matter field, however, if you have put your site under version control using Git (and I recommend you do), then you can configure Hugo to pull the date from there if lastmod isn’t set in your front matter. Just put this into your config.toml file:

[frontmatter]
lastmod = ["lastmod", ":git", "date", "publishDate"]

Then .Lastmod can be used in your templates, e.g.

<span>//Updated: {{ .Lastmod.Format "January 2, 2006" }}</span>

Note that you will have to commit to git first before this will show the right date.

Link to this sectionHTML in markdown

If can be very handy to throw in the odd bit of HTML into your Markdown files but by default it will be stripped unless you set unsafe = true in the [markup.goldmark.renderer] section of your config.toml file.

Link to this sectionSeries handling

Instead of having extremely long mega posts and then updating them, alternatively splitting posts into a series (such as this one on Hugo publishing) can make a lot of sense.

In terms of setup, all you have to do is set series = "series" in your config.toml under [taxonomies] and then in your related posts, give the series frontmatter tag the same value such as "Publishing with Hugo".

Link to this sectionList posts in series

For your posts that are in a series, you will probably want to highlight that is the case by displaying a list of all related posts. That could something like this in a template:

{{ if .Params.series }}
  {{ $series := index .Site.Taxonomies.series (.Params.series | urlize) }}
  {{ if gt (len $series) 1 }}
<section id="series">
  <summary>This is part of the series: {{ .Params.series }}</summary>
    {{ $series := .Params.series }}
    {{ $currentItem := .Permalink }}
  <nav>
    <ol>
    {{ range $key, $page := $.Site.Pages.ByPublishDate }}
      {{ if eq $page.Params.series $series }}
      <li{{if eq $currentItem .Permalink}} class="active"{{ end }}><a href="{{ .Permalink }}">{{ .Title }}</a></li>
      {{ end }}
    {{ end }}
    </ol>
  </nav>
</section>
  {{ end }}
{{ end }}

Link to this sectionSyncing local to remote

Once you have created a new post you will want to update your website proper. How you do this will depend on your hosting, but for me it’s a matter of generating the files, commit the changes to git and rsyncing it across. Et voilà:

#!/bin/bash

Reset='\033[0m'
Green='\033[33;32m'

# We do this first so that hugo picks up the modified dates
echo -e "\n${Green}Pre publish...${Reset}"
git commit -message "Pre publish."
git push

echo -e "\n${Green}Generating...${Reset}"
hugo
git commit -message "Publish."
git push

echo -e "\n${Green}Syncing remote...${Reset}"
rsync -avz -e 'ssh -p <port> -i ~/.ssh/<key_file>' ./public <remote-user>@<remote-ip>:<remote-dir>

echo -e "\n${Green}Done${Reset}"

If you are using the published date concept, you might want to throw the above into a script that gets called by cron once a day.

And there you have it. Next time, I’ll cover ways to enhance the editing experience itself.

  • //Series: Publishing with Hugo
  • //Tags:
  • //Posted: May 30, 2022
  • //Updated: August 3, 2022