Introduction

Hugo is a great framework to build your blog with.

Unfortunately, Hugo doesn’t ship a WYSIWYG editor like WordPress which makes it difficult for a person without coding experience to understand what’s going on with the framework. It becomes a bit tedious to grasp the whole picture of Hugo and its terminologies if you haven’t worked with a static site generator before.

I learnt Hugo with just the documentation and the Discourse. But docs are never fun, are they? Not everyone needs to spend so much of time reading them just for a personal blog.

So, this is a beginner-friendly comprehensive guide on creating your own blog/site. At the end of this guide, you will have your own functional, minimalist blog up and running for free. Along the way, I explain Hugo terminologies in a simpler fashion, comparing them to WordPress (my previous platform of choice) as needed.

Please Note

  • Appropriate resources have been linked wherever possible.
  • Feel free to skip a step or two if you are already familiar with them.
  • Use the Table of Contents to navigate through the guide as needed.
  • I have chosen to use Anatole theme in this guide since it’s fairly minimal. Updates to Hugo or the theme itself shouldn’t drastically make this post outdated.
  • Your result might not be exactly same as mine if you are starting with raw Anatole, because I have used some extra (not documented in Anatole’s docs) configuration.
  • I have made use of comments, in places, to explain the code. You can entirely omit them in your configs.
  • Most of the commands mentioned here assume you are in your site’s root directory.

Prerequisites

  1. Knowing basic use of Terminal/Powershell; like opening a particular path/location.
  2. Install Git
  3. A GitHub Account (Free)
  4. Install Scoop if using a Windows machine
  5. Install Homebrew if using a Linux/Mac machine
  6. A code editor. I use Visual Studio Code.

The setup process might take a long time according to your level of experience. And don’t freak out if you are seeing all this first time. You don’t need to be an expert at all these things. Just copy-pasting would do as well.

If you still don’t want to fiddle with your machine, you can try out something like Forestry or Stackbit. I haven’t used them since that would mean giving up the fine-grain control and other benefits of Hugo ;)

Sample Code to Clone/Compare

Here’s a public GitHub repo which you can clone or compare your progress with. If you don’t know what a repo is…just think of it as publicly available folder for now.

Going through the sample files and the folder structure might make the picture clearer.

And here’s the published version of the site we would build today.

Installing Hugo

Let’s begin with installing Hugo on your machine. Yep. Hugo is installed locally on your machine and not on a server. Here’s Hugo’s official Installation Guide.

The installation process differs OS to OS, but I would recommend sticking with a package manager i.e. Scoop or Homebrew to minimize chances of error. Also, make sure that you get the extended version of Hugo with the capability of SASS/SCSS processing.

To install on a Windows machine, you just need to run:

1
scoop install hugo-extended

On Linux/Mac, run this:

1
brew install hugo

Creating the Site Directory

Great! You moved past the installations. Steps, here onward, are quite easy now.

Let’s move on and create our Hugo Site’s directory, shall we?

To create one, fire up your terminal at the preferred location and enter:

1
hugo new site <your sites name> -f yml

Breaking down the command:

  1. hugo new site just creates the basic directory structure of a Hugo site
  2. <your site's name> should be replaced with anything; a folder is created with this name
  3. -f yml specifies the preferred syntax for the site’s configuration file. I recommend sticking with YAML since it is more readable.

Now, you have your site’s root directory set up. Open it and you will find the following directory structure there. Ignore any unfamiliar terms for now.

1
2
3
4
5
6
7
.
├── archetypes    # pre-configured front-matter for new posts
├── content         # all your posts and pages reside here
├── static            # static files like favicons and other images go here
├── data             # you can ignore this
├── themes        # your installed theme resides in this folder
└── config.yml      # think of it as the admin panel of your site

Here’s the official documentation to read further: Directory Structure - Hugo.

Installing Theme

I recommend installing the theme as a git submodule since it simplifies the update and deployment process.

Initializing Git

Git is a version control system used by coders to maintain their code bases. It’s like creating small restore points each time you make a change in the code so that you have a fallback plan in case you mess up.

Assuming you installed Git as per the instructions in the prerequisites section, open the terminal back at the site’s root directory and run git init. This tells git to start tracking changes there.

Also add a .gitignore file to keep Git from uploading the custom generated resources to GitHub in later stage. The file should simply contain just this:

1
/resources

Adding the theme as a Git Submodule

After initializing git, we can proceed to install themes. Open your preferred theme’s documentation and search for installation instructions. Copy/Paste the command given for git submodule integration.

For example, we will be using the Anatole theme in this tutorial. So, to install it you would run:

1
git submodule add https://github.com/lxndrblz/anatole.git themes/anatole

Configuring Our Site

As I mentioned earlier, config.yml is your site’s admin panel. All the major settings are controlled with this file. Let’s set up ours now. Copy and paste the following code in your config.yml file (replacing the existing one).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
baseURL : "http://examplesite.com" #copy-paste your domain here
languageCode : "en" #used in SEO tags
title : "Tutorial Site" #actual title of the site used in browser tabs as well as SEO
theme : "anatole" #name of the directory containing theme files
summarylength : 50 #summary length shown on list pages
enableRobotsTXT : true #leave true for SEO
#this file instructs search engine crawlers on how to acess the site

params:
  title : "I'm Jane Doe" #emphasised line on the homepage
  author : "Jane Doe"
  copyright : "2020-2021" #copyright text in the footer
  description : "Call me Jane" #introduction line on homepage
  profilePicture : "profile.png" #the name of the profile img to be displayed (relative to the static folder)
  keywords : "" #seo keywords for your homepage
  #favicons are small images displayed in the browser tab similar to app icons
  favicon : "favicons/" #directory name holding the favicons (relative to the static folder)
  mainSections : ["blog"]
  #which folder to use to list posts on the homepage (relative to the content dir)
  images : ["featured-image.png"] #image used for social media previews
  doNotLoadAnimations : false #set to true to disable animations
  postSectionName : "blog" #the slug of the "archives" listing page
  #uncomment this if you want your site to load in dark-mode by default.
  # displayMode: "dark"

  ## Social links
  #the icons are loaded from font-awesome library
  #find the icon codes here: https://fontawesome.com/icons?d=gallery&p=2
  # use 'fab' when brand icons, use 'fas' when standard solid icons.
  socialIcons:
    - icon : "fab fa-linkedin"
      title : "Linkedin"
      url : "https://linkedin.com/"
    - icon : "fab fa-github"
      title : "GitHub"
      url : "https://github.com/lxndrblz/anatole/"
    - icon : "fab fa-instagram"
      title : "instagram"
      url : "https://www.instagram.com/"
    - icon : "fas fa-envelope"
      title : "e-mail"
      url : "mailto:mail@example.com"

  # Google Search Console Verification
  #googleSiteVerify : "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
  # Read More links for truncated summaries
  # readMore : true

  ## Math settings
  math:
    enable : false  # options: true, false.

## Menu items
#weight defines which entry is shown first
menu:
  main:
    - name : "Home"
      identifier : "home"
      weight : 100
      url : "/"
    - name : "Archives"
      weight : 200
      identifier : "archives"
      url : "/blog"
    - name : "About"
      weight : 300
      identifier : "about"
      url : "/about"

taxonomies:
  category: categories
  series: series
  tag: tags

timeout: 30000
#required to exit out of long posts requiring lot of processing.

minify:
  disableXML: true
  minifyOutput: false

# Google Analytics
# Hugo supports GA4 V8.20 (latest at the time of writing) onwards.
#googleAnalytics : "UA-123-45"

publishDir: "docs"

Creating A Post

In Hugo, all your posts/pages reside in the content folder of your site’s directory. And are bundled relative to the root of your site. For example, if you have an about page on your site at examplesite.com/about you will have an about.md file in the content directory. Or if you have examplesite.com/blog/post, you will have post.md inside blog folder in the content directory.

Make no mistake though, your file structure and the permalink/URL are not strictly connected. You can override it in the front-matter of the post.

What is a URL?

1
2
3
4
5
https://examplesite.com/blogposts/example-post
                                 |---slug----|
                       |-section-|
|------baseURL---------|
|-------------------URL----------------------|

Breaking Down:

  1. baseURL is defined in the config file
  2. section is by default defined by the directory structure inside the content folder (can be over-ridden in the config file)
  3. slug is defined in the Front-Matter (is the filename by default)

What is ‘Front-matter’?

Each post in Hugo consists of front-matter. Think of it as settings of each individual post typed out at the beginning of each file. Like Gutenberg’s side panel. So, things like title, featured image, author, categories and tags would be set using the front-matter in Hugo.

In case you come across another site’s code, please keep in mind that front-matter can also be defined in TOML/JSON. See the official documentation here.

What is the deal with the .md extension?

One of the many perks of using Hugo is to be able to write posts using the efficient Markdown syntax. It is like a simple list of rules to speed up your writing process without touching any HTML. See the Markdown Guide here. And believe me, once you get to know it, there’s no going back. I learnt about it last year, and it has been quite life changing.

How to organize posts in Hugo?

You can organize your posts into separate folders and drill down as much as you need. But as we are setting up a blog, we will need just one folder to organize our posts in. Your directory structure inside the content folder would look something like this where any page outside the blog folder would be a separate page (e.g. Contact Page or About Page; similar to WordPress Pages):

1
2
3
4
5
6
.
├── about.md
├── contact.md
└── posts
    ├── post1.md
    └── post2.md

Where do the image files go?

Images, in Hugo, can be placed in two different ways. Either they can be part of a Page Bundle or be placed in a central location under the static directory like wp-media in WordPress.

Which is better? Creating page bundles unlocks the power of Hugo’s unique Image Processing Methods. Besides that, it would be just a matter of personal preference.

Currently, you don’t need to worry about the Page Bundles part. We will be keeping things simple and placing images under static directory as Anatole doesn’t use Image Processing. Your static folder should look like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
.
├── profile.png
├── favicons
    ├── favicon.ico
    ├── apple-touch-icon.png
    ├── favicon-32x32.png
    └── favicon-16x16.png
└── cover-images
    ├── cover1.png
    └── cover2.png

Creating first archetype

Archetypes are like predefined settings (front-matter) for your new posts. Why create this separately? There are many variables one can define in the Front-matter. Some variables also vary with the theme. Hence to save us from the trouble of manually going through a lengthy list of variables each time, we define archetypes. Defining archetype also helps you auto-populate dynamic fields such as Date right at the time of creating the post with just one single line. Read more about it here.

If you check your archetypes directory, you’ll find a default.md there. That’s just the basic skeleton. Let’s define our own archetype now. Create a file called post.md in the same place and copy the following content into it:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
--- #specifies the content is in YML
title: "{{ replace .Name "-" " " | title }}" #Auto-Populated Title of the Post
date: {{ .Date }} #Auto-Populated Date Field
draft: true #Status of the Post
author: "" #Author Name
description: "" #Description/Used in SEO
summary: "" #Used on Archive Pages and in RSS
images: ["post-cover.png"] #images used for social media preview. comma separate each image path enclosed in double quotes
thumbnail: "images/landscape.jpg" #featured-image of the page. i will recommend using same image for both preview and thumbnail
tags: [] #comma separated tags enclosed in double quotes. also used for SEO.
categories: [] #comma separated categories enclosed in double quotes.
series: [] #A taxonomy used to list "See Also" Section in Opengraph Templates
slug: "" #Similar to WordPress's Slug (the end part of the url)
disableComments: false #Set to 'true' if you need to disable comments for any post
---

Actually creating the first post

Type in the following into the terminal.

1
hugo new --kind post blog/post1.md

Breaking down the command:

  1. --kind tells hugo which archetype to use
  2. blog/post1.md is the relative path to the actual file we need (hugo new by default creates the file in the content directory)

Open up the file now, set the front-matter whatever you want and type in anything below the closing --- three dashes. This will be your first post.

How do I setup redirects?

Hugo has in-built support for aliases in the front-matter.

Let’s say someday, back in time, you wrote an article on over-eating. You are now updating it totally…and want to redirect users to the new page.

By defining aliases variable in the front-matter (of the new post), users that land on the old-post URL will be redirected to the new one. Example:

1
aliases: ["old-post"] #will redirect users from examplesite.com/old-post to example.com/new-post

Adding an image in between the post

Again, there are two ways to add images in between the post content. You can always go with the markdown style of adding images or use Hugo’s preferred figure short code.

Here’s an example with all the parameters I think you would ever need:

1
{{< figure src="image.png" caption="image's caption" alt="text used by screen-readers" title="text-shown at the tool-tip if user hovers over the image" >}}

To align an image, you can also define the class parameter with all the above as big , left or right (specific to Anatole theme). The following code will align the image to the right and wrap the text around it. Read more about it here.

1
{{< figure src="image.png" class="right" >}}

What are Hugo Shortcodes?

Similar to WordPress short codes, they allow you to embed various things into your post without actually writing any HTML for them. For example, if you want to embed a YouTube Video in your post, instead of adding the raw iFrame code in the markdown file, you can just write down the following short code, provide the video ID and Hugo will take care of the rest of it:

1
{{< youtube youtube_video_id >}}

Here’s the list of built-in short codes shipped with Hugo. Defining your own short-codes is easy too. A short code is, basically, a repetitive piece of HTML. Receiving parameters and processing them requires the use of Go templating, though.

Serving Site Locally

Phew! Now that we have configured our site successfully and made our first post, it’s time to see what our site looks like. Let’s do this. Open your terminal at hugo site’s root directory and simply run:

1
hugo server

Now follow the link shown in the terminal or just navigate manually at localhost:1313 and you will see your site served locally. The local server monitors the site for changes and automatically reloads the site with each change you make.

Deploying the Site

All that’s left now is deploying our site.

Understand How Deployment Works

Hugo is a static site generator. It builds your site into just one folder that includes everything else. If you are hosting the site on your own using Shared Hosting all you need is that one build folder. But GitHub Pages and Netlify require some additional configuration.

What is ‘GitHub Pages’?

GitHub Pages is a free service to host free static websites right from your git repo. There’s no catch here…except a few limitations.

But, since we are aiming at a free and friction-less hosting, I will be demonstrating GitHub Pages (easy way) configuration here.

Now, GitHub Pages is by default configured to build and host Jekyll websites. So, to avoid GitHub treating our site as one, create a blank file named .nojekyll inside the site’s root directory.

And add the following line in your config.yml file:

1
publishDir: "docs" #over-rides the default setting to build site under the public folder to support github-pages deployment

Building your Site

Now to get that final folder of your site, enter this in your terminal (at the site’s root directory):

1
hugo --gc --minify

Breaking down the command:

  1. Simply running hugo alone builds your site
  2. --gc flag deleted the unused cache files after the build
  3. --minify obviously, minifies the files

First Commit and Pushing to GitHub

A commit is like saving a snapshot in Git. You can return your entire codebase to the exact state of your commit any time later down the course.

So now that we have everything ready to be deployed, let’s create our first commit and move the codebase to GitHub. Think of GitHub as cloud-storage for your code. Only except moving files using a browser, you use your terminal.

First create a repository inside your GitHub Account. Do not initialize it with anything. Set it to Public and click next. GitHub will now give you an SSH address. Copy it.

Go to your terminal and execute the following commands one-by-one:

1
2
3
4
git remote add origin <paste the copied ssh address>
git branch -M main
git commit -m "first commit"
git push origin main

Enabling GitHub Pages

  1. Head on to the Settings section of your GitHub repo in the browser and scroll down to the GitHub Pages Section.
  2. Under source, select the main branch and the docs folder. Click Save.
  3. Don’t forget to update the baseURL in the config.yml file. Otherwise, the site won’t load.
  4. That’s it!

Congratulations! You just published your own site!

If you are happy with the results produced and are serious about your blog/website grab a custom domain name and head on to hosting with Netlify (also free).

How to update the published site’s content?

You can always make whatever you need locally and then push them to GitHub once you are satisfied. Or even make use of Git Branches.

Example Workflow:

1
2
3
4
5
6
7
#you make the desired edits
#add them for staging
git add .
#create a commit
git commit -m "edit homepage" #writing commit messages in imperative is a convention
#push the changes online
git push origin main

Other Questions

What other features does Anatole support?

  • Anatole supports both Disqus and Commento for comments.
  • It has built-in support for FormSpree Contact Forms.
  • You can add custom CSS and JS using the config file (instead of over-riding the default ones).
  • Anatole ships with both KaTex and MathJax for equation rendering.
  • Syntax highlighting is supported with Chroma.

Where do you get the icon codes for the homepage?

The icons are loaded from the Font Awesome library, and you can find them on Font Awesome’s Site.

How do you generate your site’s favicons?

this tool should help you with it.

How to over-ride theme’s styles and templates?

With Hugo, you are in charge of your site’s code. To over-ride a theme’s CSS or layout is as simple as replicating the directory structure inside your theme at the root of your site’s directory and editing the files. See Hugo’s look-up order.

Example:

Say your theme controls the home page’s style using home.css file placed inside the assets/css/ directory, then all you have to do is recreate the same path at the root of your site and place the edited file in there. Like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
. (site's root directory)
├── config.yml
├── assets
    └── css
        └── home.css
├── content
└── themes
    └── theme1
        └── assets
            └── home.css

Is there a way to get live in seconds?

Run the following in your terminal. Edit the values of the files as necessary and push back the code to your own GitHub public repository and you can save yourselves some time.

1
git clone git@github.com:cryptic-code/hugo-anatole.git

Support

  • Feel free to reach out to me on Twitter/Instagram about broken links, chances of improvement and other feedback.