Header image for the post titled Hugo: The Basics

Hugo is an open-source static site generator (SSG) designed for speed and efficiency. It offers sub-second rendering times, freedom to organize your content to your heart’s desire, and a good toolset for setting up advanced file processing pipelines. Having suffered through dealt with many SSGs before, I was pleasantly surprised by the simplicity × power factor that Hugo offers. Here I document my experimentation with this tool.


Installation is quite straight forward. You just get a binary for your system and write it into your PATH. It has a single mandatory dependency (Dart Sass), but you might also want to install GO lang, if you feel like breaking up your project into modules (but that’s for later).

Basic CLI commands

  • Create a new site skeleton: hugo new site your-site-name
  • Create a new content page from an archetype template: hugo new content/page-name.md
  • Start a preview server: hugo server -D
  • Generate site: hugo


The hugo.toml file houses all of the configuration, but it starts off pretty sparse. I found this a good place to start:

baseURL = "http://mysite.com/"
title = "A very cool site!"
theme = "cooltheme"

paginate = 10
rssLimit = 20
copyright = "Me!!!"

defaultContentLanguage = "en"
  languageCode = "en"

  name = "Homepage"
  weight = 10
  identifier = "home"
  pageref = "/"
  name = "Tags"
  weight = 20
  identifier = "tags"
  pageref = "/tags/"
  name = "Archive"
  weight = 30
  identifier = "archives"
  pageref = "/post/"
    name = "About"
    weight = 40
  identifier = "about"
  pageref = "/about/"
  name = "My other site"
  weight = 50
  rel= "external"
  url = "http://othercoolsite.com"

  keywords = ["cool stuff", "awesomeness", "just rad"]

  description = "This is my awesome site that is just so cool and rad!"


Theme selection for Hugo is pretty decent. If you want a quick start, download one into the themes folder, and just copy over the appropriate configs. For a lot of people that would be it, you have your site now! But that is not how we roll. Here, we like to suffer through our own design and templating!


Hugo expects your content to be organized within a specific directory structure. By default, Hugo uses a content directory at the root of your Hugo project. Inside this directory, you should have subdirectories representing the different sections of your site. For example:

├── blog/
│   ├── my-first-post.md
│   ├── my-second-post.md
│   └── another-post/
│       └── index.md
├── news/
│   ├── latest-news.md
│   └── older-news.md
└── about.md

In this structure:

  • blog/, news/, and about.md are sections of your site.
  • Each Markdown file (.md) represents a piece of content like a blog post or a page.
    • You can use html files as well, but it somewhat defeats the purpose of templating. It is only very useful for singular pages that present content in ways that are different from the rest of your site.
  • Subdirectories within sections (e.g., another-post/) can represent nested content (e.g., for hierarchical pages or sub-sections).

Each Markdown file in Hugo should start with front matter. Front matter is written in YAML, TOML, or JSON and provides Hugo with essential information about the content. Here’s an example of front matter in a Markdown file:

title: "My First Post"
date: 2024-04-25
draft: false

Content goes here.

In the above example:

  • title specifies the title of the content.
  • date is the publication date.
  • draft indicates whether the content is a draft (true or false).

Hugo supports taxonomies like categories and tags, which are useful for organizing and filtering content. Taxonomies are defined in your site configuration (config.toml). For example:

  tag = "tags"
  category = "categories"

You can then assign tags or categories to your content in the front matter of each Markdown file:

title: "My First Post"
tags: ["technology", "Hugo", "static site"]
categories: ["tutorial"]

Hugo allows you to define custom content types, which can have their own templates and behavior. Content types are typically defined in the config.toml file. For example:

  # Define a custom content type "product"
    name = "product"
    description = "Products for sale"
    layout = "product"
    isPlainText = false


We have content, now let’s learn how to apply a hierarchy of templates to render the content. We will go over a basic but functional example.

There is a certain hierarchy of templating in Hugo. The scope of the template defines which pages it applies to. As a basic example, head on over to the layouts folder and create a _default subfolder. In this folder, let’s create two files baseof.html and single.html.

baseof.html defines the base template of all of our html documents. It will contain html that is common to all pages on our website.


<!DOCTYPE html>
<html lang='{{ $.Site.LanguageCode | default "en" }}'>
    <meta charset="utf-8">
      {{- block "title" . -}}
        {{ if .Title }}
          {{- .Title }} × {{ .Site.Title -}}
        {{ else }}
          {{- .Site.Title -}}
        {{ end }}
      {{- end -}}

  {{ partial "head.html" . }}
  {{ block "head" . }}{{ end }}
{{ block "body" . }}
{{ end }}
  {{ partial "scripts.html" . }}

Now let’s look at single.html, a template for rendering a single piece of content (e.g., a blog post).


{{ define "head" }}
  {{ $style := resources.Get "sass/single.scss" | toCSS  }}
  <link rel="stylesheet" href="{{ $style.RelPermalink }}" media="screen" crossorigin="anonymous">

{{ define "body"}}
  <div id="menu">
      <div id="menu-links">
          {{ range .Site.Menus.main }}
                <div class="menu-item">
                  <a class="homepage-menu-link" href="{{ .URL | safeURL }}">
                    {{ .Name }}
          {{ end }}

    <main id="content" role="main">
      {{ .Content }}
{{ end }}

There is a lot going on here, so let’s break things down. Hugo uses GO lang’s text/template and html/template packages. We write the static portions of any document in the native html format, and introduce dynamic content within code elements enclosed in double curly brackets.

Blocks and Defines

The block is the most basic templating element, and it essentially behaves like a placeholder with a default value. It delimits a region within the template that can be easily substituted within the final document.

The basic structure of a block:

{{ block "block_name"}}
<p>Default content.</p>
{{ end }}

Here we define the block name and enclose the default content to appear if no override information is defined.

In any document (or a higher level template) that uses this base template, we will use the define element to populate the values of blocks with their final content:

{{ define "block_name"}}
<p>Content that will actually appear.</p>
{{ end }}

You may have noticed that some blocks in our baseof.html example look a little different.

{{- block "title" . -}}
{{- end -}}
  • The dashes at the beginning and end of the code elements are calls to the “trim spaces” function provided by Hugo. This tells the template that leading and trailing spaces introduced within the corresponding define element will be removed.

  • The dot after the block name tells Hugo that the information context from the template is being passed to the higher level document or template.

In our example, baseof.html contains blocks for the document title, the head tag, and the body of the document. In single.html we define the information that gets populated when the two templates get merged during rendering. Specifically:

  • Within the head block we use a Hugo function to render a SASS file to CSS and then to link the resulting style sheet to the page.
  • Within the body block:
    • We pull the information about the site’s menu from the page’s context, and use Hugo’s range function to iterate over and present each of the menu elements.
    • Define where the page’s content will appear.
  • We do not define the title block in single.html, which means that the default value from baseof.html will be used.


In addition to blocks, our baseof.html template contains calls to partials, which are context-aware reusable components. Partials are saved under the layouts/partials subfolder, and are referenced by their filename, e.g., {{ partial "head.html" . }}. Here is an example of a partial we use in our head tag:

{{- if or .Params.author .Site.Author.name -}}
{{- $author := .Params.author | default .Site.Author.name -}}
<meta name="author" content="{{ $author | safeHTML }}" />
{{- end -}}

Here is what is going on in this example:

  • We are using the if logical statement to check if the specific page’s front matter provides a value for author’s name, and otherwise take site wide value for author. We then assign it to the $author variable and then render it within an html meta tag.
  • We use a safeHTML function to declare a provided string as a “safe” HTML element to avoid escaping by Go templates.

We can re-use this partial in the head section of any template we develop.


Hugo’s templating language is designed to be used in the layout elements. You cannot use these dynamic features of Hugo in your content files directly. However, you can package your dynamic code within Shortcodes, function-like snippets of code that can be called from within your content files. They are essentially like partials, except you use them in Markdown, instead of layout templates.

To make a shortcode:

  • Save an html file in layouts\shortcodes. The filename is the name of the shortcode.
  • Write html and/or Go template functions and variables.
  • You can pass attributes to a shortcode. Get the positional arguments by {{ .Get 0 }}, and the named arguments by {{ .Get "namedAttribute" }}
  • Insert your shortcode in your Markdown content: {{< shortcodeName positional namedAttribute="value">}}.

Wrap up

Hugo stands out as a powerful and efficient static site generator ideal for developers looking to streamline their website development process. From its swift installation to the versatile configuration options, Hugo simplifies the complex task of site generation without sacrificing flexibility or control. The basic command line interface commands, combined with advanced templating and content organization features, empower users to create and manage sophisticated sites with ease. Moreover, the support for custom content types and taxonomies enhances Hugo’s ability to handle diverse content needs. Whether you’re a seasoned developer or a newcomer to static site generation, Hugo offers a robust framework that can adapt to various project requirements, making it a compelling choice for building fast, reliable, and maintainable websites.