How I Use Hugo For This Site

Here are the main features of Hugo I use to generate all the static pages of this site.

You might also want to see how I used Jekyll for this site before migrating to Hugo.

Root Folder

$ ls
archetypes config.toml content data
layouts resources static themes

config.toml

The config.toml file content is:

baseURL = "https://kaue.me"
languageCode = "en-us"
title = "Kaue's Way of Life"
publishDir = "../server/_site"

[markup]
  [markup.highlight]
    style = "rrt"

[outputs]
  home = [ "RSS", "HTML"]

[outputFormats]
  [outputFormats.RSS]
    mediatype = "application/rss"
    baseName = "feed"

[author]
  name = "Kaue Silveira"

This uses the rrt markup highlight style to address the accessibility problem “Background and foreground colors do not have a sufficient contrast ratio” present in the default style.

The RSS is configured to output to feed.xml.

Content Folders

This is what the content folders look like:

$ ls content/
about all goals headless_goals health
_index.md love meaning robots.txt wealth
$ ls content/health/
exercise _index.md nutrition sleep
$ ls content/health/exercise/ | head
_index.md
levered-body-weight-exercises.md
phil-maffetone-the-180-formula-....md
stronglifts-5x5-workout-and-app-review.md

Content Files

Content files look like this:

$ cat health/sleep/_index.md | head -n 11
---
title: "Sleep"
weight: 100
description: "Sleep."
---

# Sleep

{{< goal >}}

## Challenges

I have some custom shortcodes (like goals) and partials which I cover next.

Shortcodes And Partials

This is based on the official Hugo documentation, with some modifications.

{{- if not .IsHome -}}
  <hr>
    <ol class="breadcrumb">
      {{- template "breadcrumb"
          (dict "p1" . "p2" .) -}}
    </ol>
  <hr>
{{- end -}}
{{- define "breadcrumb" -}}
  {{- if .p1.Parent -}}
    {{- template "breadcrumb"
        (dict "p1" .p1.Parent "p2" .p2 ) -}}
  {{- else if not .p1.IsHome -}}
    {{- template "breadcrumb"
        (dict "p1" .p1.Site.Home "p2" .p2 ) -}}
  {{- end -}}
  {{- if ne .p1 .p2 -}}
    <li>
      <a href="{{- .p1.Permalink -}}">
        {{- .p1.Title -}}
      </a>
    </li>
    <li>/</li>
  {{- else -}}
    <li>{{- .p1.Title -}}</li>
  {{- end -}}
{{- end -}}

Goals

Some content pages (like sleep/_index.md above) include the corresponding goals using:

$ ls content/headless_goals/
awareness.md career.md exercise.md
friends.md fun.md good.md index.md
investing.md nutrition.md parents.md
partner.md saving.md sleep.md
$ cat content/headless_goals/index.md 
---
headless: true
---
$ cat layouts/shortcodes/goal.html 
{{- $headless := .Site.GetPage
    "/headless_goals" -}}
{{- $name := print (index (
    split $.Page.File.Dir "/") 1) ".md" -}}
<h2>Goals</h2>
{{- range $headless.Resources.Match $name -}}
  {{- .Content -}}
{{- end -}}

The overall goals page lists all goals like:

{{- $headless := .Site.GetPage
    "/headless_goals" -}}
{{- range site.Home.Sections -}}
  <h2>{{- .Title -}}</h2>
  {{- range .Pages -}}
    <h3>{{- .Title -}}</h3>
    {{- $name := print (index (
        split .Page.File.Dir "/") 1) ".md" -}}
    {{- range
        $headless.Resources.Match $name -}}
      {{- .Content -}}
    {{- end -}}
  {{- end -}}
{{- end -}}

Hugo automatically sorts pages by their weight.

Sub-Pages

This lists sub-pages:

{{- if .Pages -}}
  {{- $found := false -}}
  {{- range .Pages -}}
    {{- if .Pages -}}
      {{- $found = true -}}
    {{- end -}}
  {{- end -}}
  {{- if $found -}}
    <h2>Pages</h2>
  {{- else -}}
    <h2>Posts</h2>
  {{- end -}}
  <ul>
    {{- range .Pages -}}
      <li>
        <a href="{{- .Permalink -}}">
          {{- .Title -}}
        </a>
      </li>
    {{- end -}}
  </ul>
  {{- if not .IsHome -}}
    {{- if $found -}}
      <h2>Posts</h2>
      {{- range .Pages -}}
        {{- if .Pages -}}
          <h3>{{- .Title -}}</h3>
          <ul>
          {{- range .Pages -}}
            <li>
              <a href="{{- .Permalink -}}">
                {{- .Title -}}
              </a>
            </li>
          {{- end -}}
          </ul>
        {{- end -}}
      {{- end -}}
    {{- end -}}
  {{- end -}}
{{- end -}}

The all page recursively lists all pages in the site:

{{- template "posts" site.Home -}}
{{- define "posts" -}}
  {{- if .Pages -}}
    <ul>
      {{- range .Pages -}}
        <li>
          <a href="{{- .Permalink -}}">
            {{- .Title -}}
          </a>
        </li>
        {{- template "posts" . -}}
      {{- end -}}
    </ul>
  {{- end -}}
{{- end -}}

Include

This includes a file from the page resources:

{{- with .Page.Resources.Match (.Get 0) -}}
  {{- range . -}}
    {{- .Content | safeHTML -}}
  {{- end -}}
{{- end -}}

Used like:

$ cat content/...page.../index.md |
    grep '{{< include .* >}}'
{{< include cave.html >}}
$ ls content/...page.../
cave.html index.md

Layout

The layout conditionally includes dependencies only for the pages that need them, e.g.:

{{- $hasMath := .HasShortcode "math" -}}
{{- $hasMathPlot :=
    .Page.Params.include_math_plot -}}
{{- if $hasMath -}}
  loadJS("https://cdnjs.cloudflare.com/....js");
{{- end -}}
{{- if $hasMathPlot -}}
  loadJS("https://unpkg.com/mathjs....js");
  loadJS("https://cdn.plot.ly/plotly....js");
{{- end -}}

The layout also checks that all pages have a date:

{{- if .Date -}}
  <h6>Updated on
    {{ .Date.Format "2006 Jan 2" -}}.</h6>
{{- else -}}
  {{- if and
      (ne .Path "categories")
      (ne .Path "tags") -}}
    {{- errorf
        "missing .Date for page %s" .Path -}}
  {{- end -}}
{{- end -}}

The index page shows the most recent posts:

<h2>Latest</h2>
<ul>
{{- range first 7 site.RegularPages -}}
  <li>
    <a href="{{- .Permalink -}}">
      {{- .Title -}}
    </a>
  </li>
{{- end -}}
</ul>

Posts automatically include a page resource named include.html if present:

{{- with .Resources.Match "include.html" -}}
  {{- range . -}}
    {{- .Content | safeHTML -}}
  {{- end -}}
{{- end -}}

Used like:

$ ls content/...page.../
include.html index.md

Subscribe to my mailing list and get a free email course

* indicates required

Interests



Updated on 2020 Jun 1.

DISCLAIMER: This is not professional advice. The ideas and opinions presented here are my own, not necessarily those of my employer.