welcome to akagrue dot net :^)

How-to Hugo

Hello reader! I’m still figuring out how I want to style my blog posts, so this one looks not-so-great. I wanted to get the info out into the world though, so please excuse the mess.

I created this website using Hugo, a static site generator.

Overall I like Hugo, but getting started with it was challenging. The Hugo website has a lot of documentation on how the individual features work, but very little in terms of tying them together to show what the workflow of building something with it actually looks like.

Compounding that, Hugo also recently reworked the way it handles HTML templates, meaning tutorials found online have a decent chance of being out of date. So, I thought I would take a moment to write down the basics that I’ve learned while using it, in hopes that I can help others get started a little faster.

This post is written using Hugo version 0.162.1

Objective

The goal for this post is to walk you through the very basic steps of what it looks like to build a website with Hugo, specifically how to add content and customize how it looks. Hugo has many features that go beyond this, but I’m aiming to provide just enough information to get someone started and ready to explore more on their own.

As a side note, I will not be touching on Themes. Hugo Themes are incredibly useful if you want to build a functional website that looks good quickly, but each theme is essentially its own mini-tool with its own documentation. Plus, I learned Hugo without using a theme, and I think it was helpful for understanding what Hugo does vs what a theme provides.

Assumptions

I’m going to write this post with a few assumptions about you in mind, dear reader. If these assumptions do not apply to you, that’s fine! I hope you still find this information useful, but you will likely need to supplement it with your own research. (Also, keep it up! Learning new things can be hard but it’s good for you. I’m rooting for you!)

These assumptions are:

  1. You’re familiar with using the command line for your operating system, such that if I say “change the current directory to X and run Y” you’d know what I mean.
  2. You’ve already installed Hugo and can run it via the command line.
  3. You’re creating a brand new website from scratch. Getting an existing website adapted to Hugo will take a little extra effort. By the end of this post you should start to understand what’s needed, but for the sake of clarity I’m going to write assuming we’re starting from zero.

Getting Started

Okay, so you’ve got Hugo installed and you’re ready to build a website. Start by opening a command line and changing to the directory you want your new Hugo project to live in. Then create a new project using hugo new project <projectname>. Here’s what that looks like for me!

grue@phosphophyllite:~$ hugo new project example-project
Congratulations! Your new Hugo project was created in /home/grue/example-project.

Just a few more steps...

1. Change the current directory to /home/grue/example-project.
2. Create or install a theme:
   - Create a new theme with the command "hugo new theme <THEMENAME>"
   - Or, install a theme from https://themes.gohugo.io/
3. Edit hugo.toml, setting the "theme" property to the theme name.
4. Create new content with the command "hugo new content <SECTIONNAME>/<FILENAME>.<FORMAT>".
5. Start the embedded web server with the command "hugo server --buildDrafts".

See documentation at https://gohugo.io/.

Let’s open the project directory and see what things look like.

grue@phosphophyllite:~$ cd example-project/
grue@phosphophyllite:~/example-project$ ls -1
archetypes
assets
content
data
hugo.toml
i18n
layouts
static
themes

Sick, so Hugo has made a project directory and added some files and further directories to it. This sets us up to talk about Hugo Big Important Idea #1.

Hugo Big Important Idea #1

The single most useful thing to understand when working with Hugo is that it relies heavily on a software principle known as convention over configuration. This means that rather than opening a file and writing out all the rules for how your website should work (configuration), Hugo instead expects you to organize your website files in a structure it already understands (convention). A lot of learning to work with Hugo will just be finding and understanding the conventions it uses to accomplish a particular task.

Back to our example project. Right now we only need to worry about two of the directories Hugo created, content and layouts. content contains all the text, images, what-have-you that’ll go on your website. The content directory is used to build the structure of your website, and maps to the root of your website’s path. So say you write up a simple blog post, just some text about your day. You might write that post up in Markdown, and save that file at content/blog/my-blog-post.md. On your website, you’d access the post by going to <my cool website>/blog/my-blog-post.

Okay, but that’s just the text of the blog post, and we’re building a website, which means our content needs to end up as HTML somehow, right? Correct! Enter the layouts directory. This is where the HTML templates for your website will live. How the templates are combined to produce the final page is a little complex, so let’s wait to get into detail on those. Suffice it to say, all the HTML that our site uses will eventually go in there.

Alright, enough explanation, let’s get back to using the dang thing. To kick things off, let’s see what our website looks like right now, with no content added to it. To do that, run hugo server to start Hugo’s built-in development server on your machine. Here’s what I see:

grue@phosphophyllite:~/example-project$ hugo server
Watching for changes in 
/home/grue/example-project/archetypes, 
/home/grue/example-project/assets, 
/home/grue/example-project/content, 
/home/grue/example-project/data, 
/home/grue/example-project/i18n, 
/home/grue/example-project/layouts, 
/home/grue/example-project/static
Watching for config changes in /home/grue/example-project/hugo.toml
Start building sites …
hugo v0.162.1 linux/amd64 BuildDate=unknown

WARN  found no layout file for "html" for kind "home": 
You should create a template file which matches Hugo Layouts Lookup Rules for this combination.
WARN  found no layout file for "html" for kind "taxonomy": 
You should create a template file which matches Hugo Layouts Lookup Rules for this combination.

                  │ EN
──────────────────┼────
 Pages            │  4
 Paginator pages  │  0
 Non-page files   │  0
 Static files     │  0
 Processed images │  0
 Aliases          │  0
 Cleaned          │  0

Built in 3 ms
Environment: "development"
Serving pages from disk
Running in Fast Render Mode. For full rebuilds on change: hugo server --disableFastRender
Web Server is available at http://localhost:1313/ (bind address 127.0.0.1)
Press Ctrl+C to stop

You can see that Hugo is warning us about missing layouts, we won’t worry about that for now. Open http://localhost:1313 in your web browser and you should see a big Page Not Found. Well, we haven’t added any content to our project yet, so that makes sense! Let’s add some now. All good websites need a home page, so let’s start by adding that: run hugo new content _index.md in a new terminal (be sure to leave Hugo running!)

grue@phosphophyllite:~/example-project$ hugo new content _index.md
Content "/home/grue/example-project/content/_index.md" created

As requested, Hugo has created a file called _index.md and placed it in the content directory. This is our first example of convention over configuration! A file with the name _index.md has a special meaning in Hugo; it turns the directory that it’s in into a Section. A Section maps to a directory on your website’s path, so in our earlier blog example, we could create an index file at content/blog/_index.md, and its content would be shown when we went to <my cool website>/blog/. Since the _index.md file we’ve just created is at the root of our content directory, it’s displayed when we’re at the root of our website path, otherwise known as a home page!

Let’s open it and add a few words. You’ll notice when you open the file that it already has some text in it, surrounded by +++. Hugo calls this the front matter of the content. Right now we can just think of it as metadata for our page; later we can do a bunch of cool stuff with it. For the moment just be sure to change draft = true to draft = false. This tells Hugo that we aren’t still working on the page, and that it can include it in the final site. Add whatever text you’d like to be on your homepage underneath the bottom +++. Here’s what mine looks like:

content/_index.md

+++
date = '2026-08-15T17:05:10-07:00'
draft = false
title = ''
+++

Hello! This is the homepage of my Hugo example site!

Save that file, and you should see the content of your _index.md file in your browser!

Building the layouts

Okee doke, so now you have a website, and there’s some text on the home page. But it’s just boring old text, and we’re trying to build a dang website! For that we need to start creating HTML, which means it’s time to talk about layouts.

Let’s start with something very simple. Add the following to layouts/home.html (this is another example of convention, home.html is the template used for your home page).

layouts/home.html

<html>
    <body>
        Home Page Template
        {{ .Content }}
    </body>
</html>

You might notice that those funky {{ }} brackets aren’t standard HTML. It’s actually the template syntax for the Go programming language. You don’t really need to learn Go to use Hugo, the syntax is simple enough that you should be able to pick up what you need from examples in the Hugo documentation. For now it’s enough to know that Hugo will evaluate those brackets when generating the final HTML page, and that .Content means the content that matches this template ( aka our _index.md file).

So nice! We’ve got a home page for our website and we can customize how it looks. Hey, this whole experience might make for a nice blog post! Let’s write that up and add it to our site. We’ll start by creating the content file for the post:

grue@phosphophyllite:~/example-project$ hugo new content blog/learning-hugo.md
Content "/home/grue/example-project/content/blog/learning-hugo.md" created

Now, let’s open that file and write our first entry (don’t forget to set draft = false)!

content/blog/learning-hugo.md

+++
date = '2026-08-16T11:37:37-07:00'
draft = false
title = 'Learning Hugo'
+++

Dear internet, today I made my first website using Hugo!

Hugo also needs to know how to display the post, so let’s create another template in the layouts directory called page.html. This is also a special name, it’s the default template Hugo uses for individual pages, like our blog post!

layouts/page.html

<html>
    <body>
	    Page Template
        {{ .Content }}
    </body>
</html>

Once that template is saved, we should be able to go to http://localhost:1313/blog/learning-hugo/ and see our blog post! Hmm, but if we go back to our home page, our blog post isn’t listed anywhere, so it’ll be pretty hard to find… let’s fix that. Open home.html again and add the following:

layouts/home.html

<html>
    <body>
        Home Page Template
        {{ .Content }}
        {{ with .Site.GetPage "/blog/learning-hugo" }}
            <a href="{{ .RelPermalink }}"> {{.Title}} </a>
        {{ end }}
    </body>
</html>

Refresh again, and you’ll see a link to your blog post! Pretty spiffy huh? Let’s make a few more improvements! First, it’s a little annoying to have to write out full HTML tags for every template, right? We can consolidate those into another special file called baseof.html. True to its name, it’ll be used as the base of all other layouts on our website. Here’s what it’ll look like:

layouts/baseof.html

<html>
    <body>
        {{ block "main" . }}
        {{ end }}
    </body>
</html>

That syntax just means “get all of the content labeled ‘main’ and put it here”. Let’s go back and label our home and page templates so Hugo knows it should put them there.

layouts/home.html

{{ define "main" }}
    Home Page Template
    {{ .Content }}
    {{ with .Site.GetPage "/blog/learning-hugo" }}
        <a href="{{ .RelPermalink }}"> {{.Title}} </a>
    {{ end }}
{{ end }}

layouts/page.html

{{ define "main" }}
        Page Template
        {{ .Content }}
{{ end }}

Save everything and refresh! Same website, but less repeating yourself! Having a single base HTML file will also be useful later on, when we want to add CSS. A single base means we only have to link it once! Let’s celebrate the only way we know how, another blog post:

grue@phosphophyllite:~/example-project$ hugo new content blog/more-hugo.md
Content "/home/grue/example-project/content/blog/more-hugo.md" created

content/blog/more-hugo.md

+++
date = '2026-08-16T12:20:11-07:00'
draft = false
title = 'More Hugo'
+++

Dear internet, I'm on my way to becoming a Hugo master!

But, ugh, we don’t want to have to add a new link to our home page every time we write a blog post. Instead, let’s make a page that lists all of our blog posts and link to that on our home page. First we’ll need to create another special template called section.html. This is the template used when we go to one of the Section pages I mentioned way back at the start of this post.

layouts/section.html

{{ define "main" }}
    Section Layout
    <ul>
    {{ range .Pages }}
        <li>
            <a href="{{ .RelPermalink }}">{{ .Title }}</a>
        </li>
    {{ end }}
    </ul>
{{ end }}

And let’s update our home page to point to the new blog page! Note the slightly different syntax for getting a section instead of an individual page.

layouts/home.html

{{ define "main" }}
    Home Page Template
    {{ .Content }}
    {{ with .Site.GetPage "section" "blog" }}
        <a href="{{ .RelPermalink }}">My blog posts!</a>
    {{ end }}
{{ end }}

Nice! So now on our home page we link to our blog section, and our blog section lists all of our blog posts! But hey, I thought we needed to create an _index.md file to make our blog directory into a section, right? Not quite! One more piece of convention! By default all of the top level directories under content are automatically treated as sections. You can still create an _index.md file to add content or front matter to the section page, but it’s not strictly required.

Next Steps

That’s all I have for this post! Hopefully I’ve helped you get a sense for what the pattern for using Hugo will be: organize your site’s content under the content directory, then determine how it looks by creating HTML in the layouts directory. There’s still a lot to learn though! Here are some ideas for what to do next:

Style it!

For simple CSS, create a CSS file under static and link it in your baseof.html template in this way:

<link rel="stylesheet" href="{{ "css/style.css" | relURL }}">

Hugo also supports more complex CSS handling if you want to dive into that!

Page Bundles and Resources

All of our blog posts were just text, but what if we wanted to include images or other media? For that we’d need to use Page Bundles. Content organization takes a little thought in Hugo, but now that you’ve read this post you already know the basics!

Learn the layout rules!

The layout selection rules are fully defined here. Read through them to get a better understanding of how the layouts in this post worked. Not a bad page to keep bookmarked for later reference either.

Archetypes

Archetypes are templates for your content. They can save you a lot of time by creating directories and placeholder content for you to fill in. I use an archetype for all of the galleries on my website and it’s super helpful!

Hugo Directory Structure

Learning about what all those other directories Hugo created are for is a great way to understand how the framework works!

Good luck!