Noobs guide to Hugo

✍️ Wanna start a blog?#

It's wonderful to start blogging, and these days, the Internet has made it extremely easy to set things up. There are various tools available that can help to achieve the same goal, while meeting different needs.

If you don't really bother building things from scratch and only care about distribution, Medium is the first start. It has millions of readers, an engaged community, and the editor is simple and elegant. Personally, I also like Notion for its functionality and multi-use case. You can easily share anything in Notion publicly, which basically serves as a blog/website. The only downside is that Notion doesn't support custom domains yet.

However, if you fancy more customisation without too much coding, WordPress, Squarespace, Weebly and Wix are among the popular options. I've also discovered Format as a platform for creatives; and Webflow, a design-first dynamic website builder without any code, is gaining lots of traction from the rise of the no-code movement.

After all, there's no single tool that fits all, and most often it comes down to personal preference. Go for whatever that's easy for you to use and to reach wider audience.

🤔 Why Hugo?#

One of the benefits of working in tech is that you're constantly surrounded by smart people searching for alternatives - I got inspired by a coworker and started exploring alternatives to host my website without the need to pay. Then I found Hugo - a general purpose website framework written in Go. As a static site generator, Hugo is extremely fast to deploy.

I like the idea of learning through doing, and since Hugo supports both HTML and Markdown as content formats, it can help me get familiar with both, and understand how websites are built and deployed. Further down the road, I can add code blocks and enable math formulas for data related posts!

Ok, I should admit that I chose Hugo over Jekyll because Hugo has more themes that suited my taste; and I passed over Gatsby because the learning curve (JavaScript, React and GraphQL) is too steep 😂.

Anyway, after 6 months of procrastination, I'm pretty happy about the result (since last Christmas✌️). Depending on the theme you choose, different levels of customisation may be needed to make it work. I will go through the major ones in this post and hopefully this will make life easier for those keen to try out!

👩🏻‍💻 Hands on keyboard#

Hugo has a well-documented guide here to set things up, with the assumption that you are comfortable with Git and the command line. The example here uses MacOS and the theme Hugo Novela. I will leave out any stylings so the code can be applicable elsewhere.

1. Create a site locally#

This is the first step to building your own website before making it live on web:

  • Install Hugo on your machine using Homebrew
brew install hugo
  • Go to your designated folder where you want your new site to be created
-- for example, I would like to have mine under github folder
cd ~/github/

-- use the command line below to create a new site
hugo new site your_site_name
  • Choose a theme from themes.gohugo.io, here I use Hugo Novela. Alternatively, you can also download it directly via https://github.com/forestryio/hugo-theme-novela/archive/master.zip and move to themes/
cd your_site_name
git init
git submodule add https://github.com/forestryio/hugo-theme-novela.git themes/novela
  • Then add 'theme = "novela"' to config.toml to let Hugo know that you're using this theme. You can also choose to have your config in .json or .yaml.

2. Site structure and your first post#

Now that you're all set, it's time to start create a new post and understand how Hugo structures the website:

  • Folders and files under content/ will be rendered as web pages organised as follows
  contents/
    post/
      post-01.md
      post-02.md
    _index.md
    about.md
  • Images are stored in static/images and can be added to the post using Markdown

  • The theme is normally organised as default layouts containing “partials” that enrich the content in defaults

  layouts/
    _default/
      baseof.html
      list.html
      single.html
    partials/
      page/
      author/
      article/
      head/
      ...
  • To generate a new post, you can either create a new .md file in post/ or use hugo new post/my-first-post.md

  • The newly generated post normally contains the following, and you can customise this by tweaking archetypes/default.md

  ---
  title: "My First Post"
  date: 2020-01-04T22:02:43+11:00
  hero: /images/hero-3.jpg
  excerpt:
  tags:
  draft: true
  ---
  This is my first post!
  • With Hugo, changes such as adding new posts, CSS styling and etc. can be instantly viewed locally at http://localhost:1313/ with the following
-- with draft enabled
hugo server -D

-- without draft
hugo server

3. More customisation - tags and math#

Before making the site public, you probably want to make it more personal. config.toml is the good starting point, where you can change the URL, site name, enable tags/categories, add additional pages and etc. Here's my current config, noting that I use the novella theme:

  baseURL = "https://www.example.com"
  languageCode = "en-au"
  title = "YOUR SITE TITLE"
  theme = "novela"
  paginate = 6
  enableRobotsTXT = true
  canonifyURLs = true
  enableEmoji = true

  [menu]
      [[menu.main]]
          identifier = "about"
          Name = "About"
          url = "/about/"
          weight = 0

  [taxonomies]
      tag = "tags"
      author = "authors"
      preserveTaxonomyNames = true


  [permalinks]
      post = "/blog/:title/"

I have included two additional features to this theme so that I can add tags to posts and collate them under corresponding tag pages, and use math formulas in Markdown where necessary. If your theme is already supporting these features below, you can skip to the next step on hosting and deploying the site.

🏷Tags

Tags are helpful for readers to find posts under the same topic. Hugo has built-in support called taxonomies for user-defined groupings of content. If you copy the config setting from above, you can define tags in your post with tags: ["tag_01", "tag_02"]. By doing this, this post is now associated with both tags.

The next step is to render pages with posts under common tags and also a page containing all the tags. In fact, when you create tags in a post, Hugo will create list pages like \tag\tag_01. However, we still need to combine everything together through taxonomy list templates and partial templates.

  • Render tags in posts

I borrowed the code from here, and created a file under layouts/partials called tags.html. The code grabs tags defined in a post and links them to associated tag pages.

<!-- layouts/partials/tags.html -->

{{ $taxonomy := "tags" }}
{{ with .Param $taxonomy }}
  <ul>
    {{ range $index, $tag := . }}
      {{ with $.Site.GetPage (printf "/%s/%s" $taxonomy $tag) -}}
        <li>
          <a href="{{ .Permalink }}">{{ $tag | urlize }}</a>
        </li>
      {{- end -}}
    {{- end -}}
  </ul>
{{ end }}

Then you can add the following partial to layouts/post/single.html. For this theme, I added it between the article content and the next article's partials.

  <!-- layouts/post/single.html -->

  <!-- ... -->

  {{ partial "tags.html" .}}

  <!-- ... -->
  • Create list page of articles under a tag

After that, your tags should show up in the posts if defined. The next step is to reformat the default list pages so that they can display articles associated with specific tags. The original list.html is simple and renders all articles in the whole website. The logic is added below so that if a page is related to \tag, it will only render posts associated with that tag in this list page.

The code modified from hugo-notepadium.

<!-- layouts/_default/list.html -->

{{ define "main" }}
  {{ partial "articles/hero.html" . }}

  {{ if isset .Data "Term" }}
    <h2><span class="hashtag">#</span>{{ .Data.Term }}</h2>
    <ul>
      {{- range .Data.Pages -}}
      {{- if (in (.Site.Params.excludedTypes | default (slice "page")) .Type) -}}
      {{- else -}}
      <li>
          <div>
            <h2>
              <a href="{{ .RelPermalink }}">{{.Title}}{{ if .Draft }}<sup class="draft-label">DRAFT</sup>{{ end }}</a>
            </h2>
            <div>
              {{ if $.Site.Data.month }}{{ index $.Site.Data.month (printf "%d" .Date.Month) }} {{ .Date.Year }}{{ else }}{{ dateFormat "January 2, 2006" .Date }}{{ end }}</span>
              {{ if .Params.timetoread }} • {{ .Params.timetoread }} min read{{ end }}
            </div>
            <span>
              {{ if isset .Params "excerpt" }}
                {{ .Params.excerpt }}
              {{ else if gt (len .RawContent) 120 }}
                {{ slicestr .RawContent 0 120 }}...
              {{ else }}
                {{ .RawContent }}
              {{ end }}
            </span>
          </div>
      </li>
      {{- end -}}
      {{- end -}}
    </ul>
  {{ else }}
  {{ partial "articles/articles.html" . }}
  {{ end }}
  {{ partial "footer.html" . }}
{{ end }}
  • Generate the master tag page

Finally, we also need a standalone tag page that aggregates all tags, with links to individual ones. This is achieved by creating a new HTML file as layouts/_default/terms.html.

The code modified from hugo-notepadium.

<!-- layouts/_default/terms.html -->

{{ define "main" }}
  {{ partial "articles/hero.html" . }}
    <h2>{{ .Name }}</h2>
    <p></p>
    <p>
        {{ $biggest := 1 }}
        {{ $smallest := 1 }}
        {{ $max := 3 }}
        {{ $min := 1 }}
        {{ $size := $min }}

        {{ $data := .Data }}
        {{ range $key, $value := .Data.Terms.ByCount }}
            {{ $size := (add (mul (div $value.Count $biggest) (sub $max $min)) $min) }}
            {{ $size := (cond (eq $biggest $smallest) $min $size) }}
            <a class = "article-tag li" href="{{ $.Site.LanguagePrefix | absURL }}{{ $data.Plural }}/{{ $value.Name | urlize }}/">
              <span class="hashtag">#</span>{{ $value.Name }}<sup>{{ $value.Count }}</sup></a>

        {{ end }}
    </p>
  {{ partial "footer.html" . }}
{{ end }}

After all these, the tag feature should be working for the entire site 🤞, and the same steps can be applied to additional taxonomies such as categories and authors.

🧮 Math syntax

If your theme has no support for mathematical notation, here is an easy way to have it enabled through a third party math typesetting library called KaTeX.

  • Create a partial math.html under layouts/partials
<!-- layouts/partials/math.html -->

<script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.2/MathJax.js?config=TeX-MML-AM_SVG"></script>
<script type="text/x-mathjax-config">
    MathJax.Hub.Config({
            showMathMenu: false, //disables context menu
            tex2jax: {
            inlineMath: [ ['$','$'], ['\\(','\\)'] ]
           }
    });
</script>
  • Include the following partial in the template that renders posts, in my case, it's layouts/post/single.html
  <!-- layouts/post/single.html -->

  <!-- ... -->

{{ if or .Params.math .Site.Params.math }}
  {{ partial "math.html" . }}
{{ end }}

  <!-- ... -->
  • Lastly, to enable KaTeX globally, set math = true in the config; else, set math: true per individual post. Below is an example of a logistic function:

$$ \sigma(t) = \cfrac{1}{1 + e^{-t}} $$

4. Running it live on the Internet#

Hugo can be hosted virtually anywhere. I chose Netlify for its ease of use. Other popular hosting solutions will be added to the reference links at the end.

All you need to do is connect your GitHub account with Netlify, and then authorise Netlify to access the website repo for continuous development. You probably also need to configure another file netlify.toml to specify your Hugo version. For more details please see here.

💭Final thoughts#

Congrats on following along and creating your own website 👏!

To be honest, I never thought I'd be able to do all of these, let alone write a tutorial! The process was a lot more cumbersome than simply paying for a service but I do find it satisfying and rewarding in the end. Despite some hacking around, I got to understand a lot more about how a website works, fixing bugs one by one, and most importantly, realising that nothing is unlearnable (and Google is your good buddy!).

The site itself is far from perfect but I've decided to take a break from development, instead, to spend more time reading and synthesising. It is only the first week of a new decade and I can't wait for what's more to come!

Useful resources and references#

Update and Acknowledgement: As of March 2020, I have switched my theme from Novela to this one created by Jake. This site would not be possible without his amazing work 🙌.

Jake has recently open-sourced the theme for Hugo community! Check it out if you're interested!