Nick George
all/

Hugo tips for the new year

First published: January 7, 2023
Last updated: March 11, 2023

I’ve updated my blog again! I have a bunch of unfinished draft posts from 2022, but with my new job and other life events I never got around to finishing and posting anything. I’m hoping to have more to post here this year, so naturally I spent the past week tweaking my Hugo theme for the new year. Here are a few Hugo templating tricks that took me a bit to figure out but improved my workflow and website structure.

Contents ¶

Creating an archive page organized by year ¶

I’ve been posting on this site since 2017, and I wanted to make an archive organized by year. This was actually more challenging than I thought, but here is how I ended up building my archive page page:

{{ range .Site.RegularPages.GroupByDate "2006" }}
  <h1>{{ .Key }}/</h1>
  {{ range (where .Pages.ByDate.Reverse "Type" "!=" "list") }}
  
  <ul>
    <li>
    <a href="{{ .RelPermalink }}">{{ .Title }}</a><br/>
    <em> Published: {{ .Date.Format "January 2, 2006" }}</em><br/>
   <em>Last updated: {{.Page.Lastmod.Format "January 2, 2006"}}</em>
    </li>
  </ul>
  {{ end }}
{{ end }}

First, we take all the “regular” pages from all sections, group them by year, and iterate over them via range .Site.RegularPages.GroupByDate "2006". The iterator is a map (I think) with a grouping variable as the key and the contents as values (likely a list?). I used .Key (the year in this case) as the top heading, then I did a reverse chronological sort of that year’s pages, pull some extra metadata and format each entry as an item in a bulleted list. Also note that I filtered any pages I manually classified as lists (e.g., the archive page itself), because it was still showing up in the RegularPages group for some reason.

I think it looks pretty good, but it took a lot of searching and experimentation to figure it out.

Only include in production ¶

There are some things in your site that you only want included in the “production” build. A good example may be analytics. At the moment I use the Clicky free tier1. In Hugo, you can check if you are in development mode using hugo.IsProduction. So in my footer partial, I have:

{{ if hugo.IsProduction }}
<script>
    var clicky_site_ids = clicky_site_ids || []; 
    clicky_site_ids.push(<analytics id>);
</script>
<script async src="//static.getclicky.com/js"></script>
{{ end }}

Now Clicky’s script won’t be included when I am running hugo in development mode2.

Formatting HTML content in a template ¶

I appreciate the ability to link to specific sections of pages when I am trying to share something with others. A heading anchor is a link to a specific markup ID within a page, it will add a #id to the end of the URL (like this). My <h2>’s markdown entries are auto-generating ID’s based on the text in the tag, but I still needed to create the links and style them. Luckily, I found an excellent guide from logfetch.com, and with a few changes to their code, I came up with this:

{{- with .Content -}}

{{ . | replaceRE "(?s)(<h2 id=\"([^\"]+)\">)(.*?)(</h2+>)" `${1}<a href="#${2}" class="hanchorlink" ariaLabel="Anchor"> ${3}</a><span class="hanchorsymbol" ariaLabel="Anchor">&nbsp;&para;</span>${4}`| safeHTML }}
{{- end -}}

The code itself is using the Hugo replaceRE function and regex groups to capture and reformat a link and a symbol to the header, while adding some class values for styling.

The cool thing about this is you can do any kind of HTML transform you want. Hugo is inserting the content post translation from markdown to HTML, and you can use the with .Content block to apply all kinds of transforms like this regex replace to generate links.

I don’t want the links to be intrusive, but I do want an indication you can click and link to them. The CSS is what makes this all look nice (I think so at least):

    /* anchors and decorations */

    h2>a.hanchorlink {
        color:black;
    }

    span.hanchorsymbol {
        visibility:hidden;
    }

    h2:hover>span.hanchorsymbol {
        visibility:visible;
        color:grey;
    }

Embedded CSS ¶

Speed and simplicity are important to me, and including CSS within the HTML document itself (in the <style> tags) should reduce page load and render times because the browser doesn’t need to hit the server twice (once for HTML, again for CSS). Since I use custom CSS that is the same for every page, why not just include it? You can insert CSS (or JS or whatever) using:

{{- readFile "/static/css/custom-grid.css" | safeCSS -}}

However, this makes it difficult to debug and update my CSS, since the readFile only happens once when the development server starts, so I had to re-start my dev server whenever I changed the CSS. To get around this, I combined readFile with the IsProduction check in my header partial:

{{ if hugo.IsProduction }}
<style>
{{- readFile "/static/css/custom-grid.css" | safeCSS -}}
</style>
{{ else }}
<link rel='stylesheet' href="/css/custom-grid.css">
{{ end }}

Now, I can edit my static stylesheet and get live-reload responses during development, and in production the build will insert the CSS directly into the style tags.

Embedded SVG ¶

Want to embed an SVG image in your website? Use the following shortcode in layouts/shortcodes/svg.html:

{{ $svg := .Get 0 }}
{{printf "/assets/svg/%s.svg" $svg | readFile | safeHTML }}

Ensure the SVG is saved in /assets/svg, and pass the SVG name (sans .svg) to the shortcode in your document:


{{< svg SVG_logo >}}

And the SVG will appear in your document! You can picture doing scaling, or replacing values etc., but this is the simplest case, where the svg is already formatted and ready to go.

Hugo is awesome, I hope this is helpful!


  1. But self hosted GoatCounter looks interesting, and I may give that one a shot soon. ↩︎

  2. See Tyler Smith’s post on dev.to if you want to learn more about working with this feature. ↩︎