Hugo is a static site generator. Well what does this mean? It means that it is a framework which takes markdown files and translates it into HTML pages. You can apply themes or simply create your own look and feel. The engine is quite powerful and you can get a lot of automation and tricks out of it if you want. It is written in go and super fast. Generating the static files is done in ms, even for huge sites or a huge collection of markdown files.

The difference between static websites and dynamic websites is that static sites consist of “pure” html and css files. We could now argue if adding client side javascript is counting as dynamic as it adds some level of dynamically, however in this context dynamic sites are usually powered by server side code which reads data from a database and renders the frontend according to the situation. Nowadays most of the sides are dynamic. Even though they do not necessarily need to be. Let’s take a blog for example.. Many will consider wordpress. However wordpress is huge.. it is complex. Furthermore dynamic sites are always a bit slower than pure static sites. This should be obvious as you do not need to process any requests and then render the front end. Another factor is security. Dynamic sites as I said are in many cases quite complex as you have to deal with database, authorization and so on. This opens up all kinds of possible exploits and gaps are easily overlooked. If your page is static you simply remove some of the attack vectors. It also reduces the effort of creating a proper runtime environment. The only thing you need for a static site is some sort of webserver and some storage.

So before you decide to go for a dynamic site, check your use case. In case you do not really need it go for a static site.

Should you decide for a static site you will end up looking for ways how to generate them, as writing everything in plain html and css can be quite cumbersome and slow. This is where a static site generator like hugo comes in. It takes away a lot of the repetitive hassle when it comes to static sites. You write your pages simply in the markdown format and once you are ready hugo generates the html files accordingly.

How to start with Hugo

Before you continue reading I just would like to highlight that hugo has quite a good documentation and very active community on their main page. Still the initial learning curve is quite steep and I spent some time to figure out the details for me.

As I migrated my page from Wordpress to Hugo I faced some challenges regarding how to add certain functionality and so on. Therefore I started with a short collection of information and descriptions on how to do things. Maybe this is helpful for you as well.


If you run on Manjaro linux, simply install Hugo via the package manager pacman. Command: sudo pacman -S hugo

For Mac, Windows or Linux in general follow the instructions here

Hugo is a commandline tool. There is no graphical interface per default. Be aware of this. You should know some basics when it comes to the command line. Be it Windows or Mac or Linux based systems.

Getting started

Once Hugo is installed the very first step is to always create a new “Hugo Project/Page”. The hugo program generates a project folder containing default files and folders for running a site.

To create a new Hugo project / site run the following command in your console (replace with the name of your project):

hugo new site <SiteName>

A new folder will be created in your current directory.

Before you can now display the page you need to install a theme or create your own. Creating your own requires already some knowledge of hugo. It is not overly complicated but we will skip it for now. Just start with an existing theme and adapt it. Once you did this and know your way around you can look into creating your own.

Select one of the many existing themes.. Don’t worry if it is not a 100 % fit, you can completely adapt it if you want. Here you can find the official hugo themes.

The theme I used and later modified is available via: git clone
Alternatively you find it on the gohugo themes page, here.

Once you decided on a theme you need to download it into the “themes/” folder of your project.

After downloading it you now need to tell hugo to load this template. To do this you need to adapt the main configuration file. The file name is “config.toml” and it should reside in your base project folder.

In my case I simply had to add the line below to my “config.toml”:

theme = "m10c"

Save the change and start the server via

hugo server -D

The flag -D will also consider draft files/posts, otherwise only pages which are not in draft mode will be displayed.

ATTENTION: Depending on the selected theme you might need to add additional configuration information to the configuration file. This information can usually be found on the theme main page. All of them come with a small documentation.


I already mentioned themes previously. You need a theme in order to tell hugo how to display content. You can either create your own (requires some technical knowledge) or simply re-use existing ones.

Within a theme you will find templates. Templates are html skeletons which provide the same overall structure to different content. You can define several templates and mix them if required.

If you use an existing template you can easily adapt it by overwriting existing theme templates and layouts. To do so you can simply add your own templates in the layouts folder:

  1. Create a folder with the name _default <= this is important
  2. Create a file with the name list.html and add your code This will overwrite the themes template. Alternatively you can also simple edit the existing template code itself. However this might break the template if you do not know what you are doing.

To add menus if it is not supported per default in your theme, you need to likely edit the baseof.html of the template and maybe add an additional block to handle menu, categories tags etc..

Tools to help you with migration

As I mentioned earlier I migrated from Wordpress to Hugo. Since I did not want to create now post again manually I looked for tools to support this. My blog is relatively small so even in the worst case scenario I would have migrated manually, but for bigger pages this is no option. On the Hugo homepage I found several tools to support migrations.

There are quite some tools to support migrations from various sources. The most important ones are related to WordPress, Medium and Blogger.

For wordpress I decided to use the plugin wordpress-to-hugo-exporter. This plugin converts all posts and pages from WordPress to a Hugo format. Running the plugin creates a “zip file” containing the post markdown files as well as the downloaded images and a config.yaml file. You can then simply copy and paste it into the appropriate places of your Hugo project folder.

In order to install the plugin you need to upload it to the wp-content/plugins/ folder in your wordpress installation. My WordPress instance was running on a Linux server to which I had SSH access. Therefore I uploaded it via console and the following command:

scp -r <local dir of plugin> ssh <user>@<server>:/wp-content/plugins/

Depending on your setup you maybe need to upload it via FTP or other means. Afterwards activate it and then run it.

Understanding Hugo and the structure

This is the most important aspect when starting with hugo. Once you have a grasp about the structure and what belongs where your life becomes immediately easier.

A hugo project has the following standard folder structure:

  • root <= Name of your project folder
    • archetypes
    • content
    • data
    • layouts
    • static
    • themes
    • config.toml

Each folder fulfills a purpose and contains specific files:

Define common meta data about content. I will explain this in more detail below. The gist of the story is that you can define some kind of templates for each of your posts / pages.

Usually all of your content or your posts of your page are placed within this folder.

Data files, such as json or other data related files which are not content should be stored in this folder.

Define layouts which can be repeatedly used across website. If you use a theme you can have a look at the theme folder. Check out the layout files and you get a better understanding. You can create templates which can be easily re-used. This should not get confused with archetypes, as archetypes only influence the meta data of new pages or posts.

All static elements such as images, css, js, and so on should be stored here. This data does usually not change much.

Themes will be located here, create your own or download it from the hugo themes page.

config.toml Main settings file for your website.

Content & Pages

Content is stored in the content/ folder of a hugo project. Via the comannd hugo new <filename>.md hugo creates a new file in the content folder. In the header of the file you will find frontmatter. Frontmatter is a kind of meta data used by hugo. For example title, date, draft status. You can add your own meta data not only manually but also via templating in archetypes.

Alternatively you can create a file in another sub directory of content. The command is similar, you just need to add the directory name before the filename hugo new newDirectory/

In general hugo differentiates between 2 types of content:

  • Single content/pages A single page showing specific content
  • List content/pages A page which lists other content. Example list of posts.

List pages for directories are automatically created by Hugo. However this is only true for the root level. Sub directories will not get a list page created automatically. You can do this manually by adding an empty file with the name in the corresponding subfolder. This naming convention has to be followed, otherwise this automation does not work. Just by creating the file via hugo new dir/subdir/ you create a list page for the sub directories. You can also add content after the frontmatter. This content will then be shown before the list. This can also be done for automatically generated list pages. Just create an empty and edit however you need it.

The theme you use impacts of course how such pages are displayed.

What is frontmatter?

Frontmatter is nothing else than meta data. The meta data is usually added to your content pages. It consists of key value pairs, which are used by hugo and the templates to display information about a page or handle it in a specific way. Hugo uses YAML per default in the content files. The meta data section is started and ended with ‘—'. This indicates that YAML is used. If you want to use TOML instead you can replace this with ‘+++’ in the beginning and end of the meta data section. Below is an example for a standard auto generated YAML meta data section in hugo:

title: "My First Test"
date: 2020-06-25T06:38:36+02:00
draft: false

Depending on your theme the meta data is displayed on list pages and content pages.

You can create your own frontmatter variables and access them later. Some templates already offer specific additional variables to enrich information about your content.

Via archetypes you can influence what meta data / frontmatter information is used during generation of a new content page.

archetypes and how to use them

Via archetype you can influence what frontmatter hugo generates for your content pages. In the archetypes folder you will find a file with the name “". If you open it you will find that the content looks quite familiar:

title: "{{/* replace .Name "-" " " | title */}}"
date: {{ .Date }}
draft: false

Let’s compare it to an actual post/content file:

title: "My First Test"
date: 2020-06-25T06:38:36+02:00
draft: false

In this example the archetype uses some hugo variables and functions to build the title from the file name as well as insert the date of creation. Additional the parameter “draft” is set per default to true.

You could now for example add a new key and value pair or edit the existing entries to your liking in the “” file. During generation of a new page/file this will now be included in the frontmatter.

You can take this even one step further by creating various archetypes. For example you can create an archetype for a specific directory. This will ensure that if a new content file is created for a specific directory it will take this frontmatter template. This can help reduce efforts if you have for example different authors for different categories. If you structure your categories via directories in the content folder. Simply create files with the directory name in archetypes and set the data which should be used. If you then generate a new content page for a specific folder this meta data will be generated. No need to touch it manually. Example:

Create a file with the name in the archetypes folder. Now add whatever information you require. In the example below i simply add an author with a name.

title: "{{ replace .Name "-" " " | title }}"
date: {{ .Date }}
draft: false
Author: "Max Mustermann who only edits dir1 posts"

Now if I execute hugo new dir1/ hugo generates the content file with the following meta data:

title: "AwesomePost"
date: 2020-06-26T22:33:55+02:00
draft: false
author: "Max Mustermann who only edits dir1 posts"

How to use hugo shortcodes

Shortcodes are just junks of predefined HTML code. Shortcodes are supposed to make your live easier for repetitive tasks such as adding a youtube video in your markdown file.

Hugo has several predefined shortcodes. You can also generate your own shortcodes if required.

Example usage:

  • Shortcodes in your markdown are written in brackets like so {{< Shortcode-name param1 >}}
  • Example for youtube {{ < youtube 2xkNJL4gJ9E > }} The youtube shortcode requires the video id as parameter. The video id is visible in the youtube url after “watch?v=". Example => Id = 2xkNJL4gJ9E

A list of build in hugo shortcodes can be found here.

Taxonomies or how to organize content in hugo

Hugo provides us with 2 taxonomies, “Tags” and “Categories”.

  • Tags are keywords you tag your blog post with
  • Categories are for grouping content under header structures

All taxanomie information is set in the frontmatter. For tags or categories simply add:
tags: ["tag1", "tag2", "tag3"]
categories: ["cat1", "cat2", "cat3"]

Depending on your theme, tags or categories will be displayed on your page. Hugo will generate a list page per tag or category. So users can easily access related pages.
For tags the path is <baseURL>/tags/<tagname>.
For categories the path is <baseURL>/categories/<category name>.

You can easily create your own taxonomies in case you need an additional way to categorize your content. To do so simply add a key-name with an array of values (could also be a single value). Example below:

awesomeness: ["meh", "wow", "awesome"]

In order to generate a page which lists all content related to the taxonomy you created, you need to configure hugo in the config.toml and tell it about the new taxonomy.

To do this add the [taxonomies] array and type out all categories including the standard ones and new ones. If you only use the standard taxonomies you do NOT need to do this.

Example config.toml excerpt below:

baseURL = ""
languageCode = "en-us"
title = "The Progress"
theme = "m10c"

  tag = "tags"
  category = "categories"
  mood = "moods"

Once you added this you will find that hugo generates the list page for the new taxonomy. You can also display those taxonomies on your website depending on your template settings.

Add your own menus in hugo

There are several options how to add menu entries. The rendering strongly depends on your theme. When it comes to manually defined menus in the config.toml I found that the following is for me the best as it allows me to be very precise. If you go for generated menus the ordering and so on might not be what you would like to have.

    identifier = "home"
    name = "Home"
    url = "/"
    weight = -500
    identifier = "about"
    name = "About me"
    url = "/about-me/"
    weight = -490
    identifier = "posts"
    name = "Blog Posts"
    url = "/posts/"
    weight = -490
    identifier = "legal"
    name = "Legal Disclosure & Privacy Policy"
    url = "/legal-disclosure&privacy-policy/"
    weight = -100


As hugo generates static sites you have usually less problems with cookies as there are simply none. However once you want to use 3rd Party services or JavaScript for Google Analytics or similar features you need to deal with this. Hugo comes out of the box with nice GDPR settings and the possibility to disable the tracking. However if you want to enable it and give the user a choice you need to implement your own Cookie Consent logic.

Details about hugo’s GDPR settings and features can be found here

As hugo is simply generating the page according to your settings there is no way to dynamically influence this. Therefore you need to come up with your own solution.

I implemented it via implementing some javascript in a partial which is used on the main page. A partial is a kind of template you can define for HTML or JS which is repeatedly used. As this is a bit more advanced I will not cover it in more detail here.

To enable the cookieconsent button I set the googleAnalytics attribute in the config.toml but deactivated it per default as I wanted to control it via my JS.

Below an excerpt of the config.toml settings as well as an link to the partial code for the JavaScript popup:

googleAnalytics = "UA-97816458-3"
# GDPR settings
#   [privacy.disqus]
#     disable = false
  #   anonymizeIP = true
    disable = true #Disable hugo GA logic, use theme logic

The code for the cookie consent button can be found here.

To use the partial in the base of your page simply import it via {{ partial "GDPR_Popup.html" . }}.

Important configuration settings and pitfalls

While I tried to move my page from wordpress to hugo I ran into some smaller issues or questions. Below I tried to create a list of the problems I ran into and how to solve them.

  • Render HTML in Markup file Per default Markdown supports HTML. Usually you do not need it but in some cases you need to add some code to make it look the way you want it. Since release Hugo 0.60 you need to set the following parameter in your config.toml to enable this. Otherwise HTML will be omitted.
      unsafe= true
  • Security and privacy settings Hugo has some built in settings to handle privacy for some standard integrations. Below are the most common settings.
        disable = false
        anonymizeIP = true
        disable = false
        respectDoNotTrack = false
         useSessionStorage = false
         disable = false
         simple = false
         disable = false
         enableDNT = false
         simple = false
         disable = false
         simple = false
         disable = false
         privacyEnhanced = false
  • Enable pagination Pagination is always lazy, therefore you need a partial or template that supports it. To enable it set Paginate = 5 or change it to the value you need.

Add Content management via netlify CMS (open source and standalone) without a Netlify account

In some cases you do not want to just use a texteditor. For example if you build a blog for a customer it is likely that the customer would like to be able to add & edit entries by himself. To make that easier you can implement a CMS. In this example I will implement Netlify CMS. The CMS is completely open source and does not require an account. For authentication I will use github and for hosting my page i use firebase. You need to implement your own Oauth handling in that case. Luckily Herohtar created already a cloud function for this. The easiest way to set this up is to use Netlify directly, however in that case you need to create an account and also connect your github account.

Below I describe the steps I followed to add it to my page with own authentication. Most of it is also described on the netlify cms page.

  1. Create the folder static/admin/
  2. Add the files index.html & config.yml
  3. Add the below index.html code. This loads the CMS script
<!doctype html>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Content Manager</title>
  <!-- Include the script that builds the page and powers Netlify CMS -->
  <script src="^2.0.0/dist/netlify-cms.js"></script>
  1. Add the config.yml code. You need to adapt this to your needs and settings… below my configuration
  name: github
  repo: jonny74889/TheProgressHugo  #add your own Repo which contains your page
  branch: master

# you can specify a target branch for changes.. without master
# will be used for commits
# publish_mode: editorial_workflow

media_folder: static/images/uploads
public_folder: images/uploads

  - name: "posts"
    label: "Posts"
    folder: "content/posts"
    create: true
      - { label: "Title", name: "title", widget: "string" }
      - { label: "Date", name: "date", widget: "date" }
      - { label: "Draft", name: "draft", widget: "boolean", default: false }
      - {label: "Featured Image", name: "featured_image", widget: "image"}
      - { label: "Tags", name: "tags", widget: "list" }
      - { label: "Body", name: "body", widget: "markdown" }


  1. Creating the GihHub App for Authentication. Go to the GitHub developer settings.
  2. Create a Create an Oauth app
  3. Leave callback URL blank for now (enter a space otherwise you can’t create it). This will be taken from the firebase setup
  4. Note the client ID & Client Secret
  5. Create the firebase function which handles OAuth and add it to your project
  6. Download/clone the Repo here git clone
  7. navigate in the cloned directory and execute firebase init
  8. Make sure you are logged in to firebase firebase login
  9. Configure cloud function with client id and secret that we got from Github via the following command chain firebase functions:config:set oauth.client_id=yourclientid oauth.client_secret=yourclientsecret
  10. deploy to current firebase project firebase deploy --only functions
  11. Add the callback URL to your Github Oauth. It should look similar to If you have a custom domain you need to adapt it to your custom domain name.
  12. Add the function url to the config.yml
auth_endpoint: /oauth/auth

The main information about Github Oauth integration I found on this helpful post

Those are some basics and tips to get you started with hugo. Play with it, its fun ;)