Hugo's Directory Structure Explained
When I first started working with Hugo I had some difficulty grasping the relationships between certain directories in my project. It can feel like there is a lot of abstraction going on in the background that you're not aware of. Things like naming conventions and lookup orders that you wouldn't know about without diving headfirst into the official documentation.
It took some time, but I can definitely say that the architectural decisions made by the Hugo team feel so intuitive. The default directory structure is light, containing only a few folders at the project's root along with a config file holding some meta information. When you start a new project it will look like this:
archetypes/
content/
data/
layouts/
static/
themes/
config.toml
This post goes over what I consider to be the core of the Hugo directory:
- Content
- Layouts
- Archetypes
- Static
I'll explain each one in order of "most useful", which is completely subjective, but understanding a certain directory's purpose will help you understand the purpose of the next.
A note on themes
Hugo allows for theme creation and they have a number of community-built themes that you can choose from if you don't feel like doing the leg work. This is apparent when going through their Quick Start guide.
Working with existing themes or building your own adds an additional layer to the directory structure of your project. It is not something that I will cover in this post. If you're looking to build a theme or alter an existing one then this post may not be for you. That being said, if you understand how the above directories work you'll have no issues working with themes in Hugo.
Content
The content/
directory holds all the content of your site.
Who would've thought.
Seriously though, I'm writing this post in the content/
directory right now.
It is made up of the sections and static pages that structure a website.
There's a distinct difference between the terms sections and static pages and it's pretty easy to grasp:
A section is a portion of the site that holds subcontent, such as a blog section and a work section. The blog section contains individual blog posts, and the work section is a portfolio of individual projects.
A static page is simply a page that renders content and nothing else. It doesn't need to index a list of subcontent for the user to peruse. The about page is a good example. If you view it in the browser, you can't go down any further. It's the about page and that's it.
So, if you were tasked to create a simple website containing this structure:
- Homepage
- About
- Blog
- Contact
- Work
Your content/
directory would potentially look like this:
content/
blog/
work/
about.md
contact.md
_index.md
The about and contact pages are static and are therefore defined as .md
files
at the root of content/
. Blog and work are sections of the site containing
subcontent, so they are structured as directories.
The _index.md
file plays an important role here. Hugo requires that an
_index.md
file be explicity defined at the root of content/
, and it uses
this file to render the homepage of your site.
Content types
All markdown files that exist inside content/
, whether at the root or in
subdirectories, have a type
associated to them. Hugo infers a markdown file's
type
in one of two ways:
- The name of the subdirectory that the file resides in
- The
type
variable defined in that file's front matter (optional)
The former is pretty simple to understand. Hugo will initially deduce a markdown
file's type by simply looking at the name of the subdirectory it is in. The file
a-blog-post.md
located at content/blog/a-blog-post.md
will have the type
blog
. If it were located at content/posts/a-blog-post.md
it would have the
type posts
.
You can override this behavior by defining the type
variable inside of the
file's front matter. We
won't be explaining front matter in this post, but it's basically metadata
defined at the top of a file such as its title
, date
, and description
.
There are a lot of cool things you can do with front matter and I'll list a few
later on, but just know that you can explicitly define a file's type here and it
will override the default type inferred from the subdirectory it is in.
So if you're wondering: Yes. You can have a file at
content/blog/a-blog-post.md
with a type explicitly set to pizza
.
List pages vs single pages
Another concept to understand about the markdown files that exist in the
content/
directory is that they can represent list pages or single
pages. To illustrate this, let's continue to use the example given above.
You now know that the blog section must be defined as a subdirectory of
content/
. It will contain individual blog posts written as .md
files:
content/
blog/
a-blog-post.md
another-blog-post.md
_index.md
Notice that there are two posts and an _index.md
file inside of blog/
. The
posts represent single pages in the blog section. The purpose of _index.md
is to index these posts and is therefore considered a list page.
This concept holds true for all subdirectories of content/
. Each one must
contain an _index.md
file which gains access to all single pages in that
section. This access grants the _index.md
file the ability to list out all the
single pages.
If you navigate to www.yoursite.com/blog
, the _index.md
file will be used as
the content for that specific route. It will render a list of blog posts that
exist in the blog section. Pretty neat, huh?
Layouts
The layouts/
directory is nothing more than a directory of templates. Each
provides a consistent layout when rendering the markdown files that exist in
content/
. It is, in my opinion, the most crucial directory in a Hugo project.
I feel this way because although Hugo has a set of rules as to how templates
should work, the developer can implement them in a number of ways.
List templates vs. single templates
Are you starting to see a trend? Lists and singles are one of the foundations of
Hugo and they are at the heart of the layouts/
directory. List templates and
single templates do exactly as they sound: render list pages and single pages.
Template lookup order
To understand the layouts/
directory you must understand how Hugo resolves
which template to use to render content. It does this by using a lookup
order, or a list of filepaths to inspect, hoping to find an appropriate
template.
In theory, your project could run on only two templates: a single and a list
template. This is sufficient enough to handle all the content on your site. Your
layouts/
directory would then look like this:
layouts/
_default/
single.html
list.html
Hugo looks in the _default/
directory if it cannot find any other template to
use to render a specific piece of content. Think of _default/
as Hugo's "last
resort". A quick tip: when creating a Hugo project for the first time your
layouts/
directory will be empty. This means that you must create the
default/
subdirectory yourself.
If you decided to go this route it would mean that all list pages would render
with the same template. This holds true for single pages as well. Why? Well,
you've only given one template for each page type. Hugo will just resolve to
using that template when it finds itself at the /default
directory during the
lookup order.
This may not seem like an issue until you want to structure your
content/blog/_index.md
list page differently from your
content/work/_index.md
list page. How would you handle that?
Templates based on content type
Since Hugo gives you a number of ways to create "different" pieces of content,
it must also give you a way to uniquely template them. Take a step back and
think about how the content/
directory was structured:
content/
blog/
work/
about.md
contact.md
_index.md
Remember content types? This is where they start to come in handy. If you want
to use unique templates for specific content types, you can create
subdirectories inside layouts/
for each type. For example, if you wanted the
blog list page and all the blog single pages to use a separate set of templates
from the rest of your site, you could define those templates in a
layouts/blog/
directory:
layouts/
_default/
list.html
single.html
blog/
list.html
single.html
During the lookup order Hugo will resolve to use layouts/blog/list.html
template for the blog list page and layouts/blog/single.html
for all of the
blog posts. Everything else will just inherit the templates inside
layouts/_default/
. Useful.
Let's assume that you want to extend this template specificity for every content
type on your site. Your layouts/
directory would continue to "mirror" your
content/
directory by accepting subdirectories named after each content type.
layouts/
_default/
list.html
single.html
blog/
list.html
single.html
work/
list.html
single.html
Looking good, but what about the about page and the contact page? They would
currently be rendered using the _default
templates. That is sufficient enough,
but I'm going to push a personal strategy on you:
I think its best to explicitly define templates for each content type you have on your site.
So let's do that. But first, a question. What content type does the about page and the contact page actually have? Remember how we said that content types are determined in one of two ways:
- The
content/
subdirectory it is in - The
type
variable defined in the file's front matter
Well, the about page and contact page aren't in a subdirectory of content/
, so
number one doesn't help us. But, we can appease number two by defining a content
type in each page's front matter.
As I said before, this post doesn't cover front matter in depth, but this
occasion requires a slight detour. For content pages that live at the root of
content/
, I like to explicitly define the type
variable inside their front
matter with a value of static
.
## type: "static"
I got this idea while reading one of Sara Soueidan's blog posts about migrating her site to Hugo. It was a massive help for me at the beginning.
Once we define the type
variable inside a file's front matter we can create a
subdirectory inside layouts/
in order to template those files directly:
layouts/
_default/
list.html
single.html
blog/
list.html
single.html
work/
list.html
single.html
static/
list.html
single.html
This paradigm is really powerful and makes working with Hugo an absolute breeze.
Homepage templates
There's another page inside of content/
that we haven't discussed: The
homepage. Just like the homepage gets its own markdown file inside of
content/
, it too can have its own template file: layouts/index.html
:
layouts/
_default/
list.html
single.html
blog/
list.html
single.html
work/
list.html
single.html
static/
list.html
single.html
index.html
Hugo doesn't require this, but I like to err on the side of explicit where
possible. If you forego a layouts/index.html
file, Hugo will then look to
layouts/_default/list.html
to render the homepage. At first I didn't
understand why. I figured the homepage was a single page and would be rendered
with a single template. But it's not.
It's actually a list page. In the content/
directory the homepage is
_index.md
, sharing the same name with all other list pages in content/
subdirectories. It lists that which exists at the root of content/
: ipso
facto your entire website.
Base templates
Here's an interesting tip that took a while for me to discover in the Hugo docs:
You can have a base template which all other templates inherit from. This
template is the Queen Bee, and she is named baseof.html
.
There can actually be a number of these baseof.html
files in your layouts/
directory, and
Hugo's lookup order for base templates
can get a bit confusing. That's why I choose to have only one located at
layouts/_default/baseof.html
.
This file is the master template that contains a lot of the standard html I wouldn't want to include in every template. Things like the document head, stylesheets and js files. I declare them once and all other templates inherit from it.
I'll be writing a post specifically on the relationships between all of these templates soon so be on the lookout for it!
Archetypes
One of Hugo's main draws is that it gives the developer the ability to create
new types of content quickly. Archetypes make this possible. The archetypes/
directory can contain markdown files named after content types on your site.
Examples of this would be: blog.md
, work.md
, and of course, pizza.md
.
Inside these "archetypes" are default parameters defined via front matter that
every new piece of content associated with this archetype will inherit. When you
first start a new Hugo project the archetypes/
directory will only contain a
default.md
file. All new content files generated via the command line will
inherit from this archetype unless an archetype exists specifically for that
content type.
Generating content files with archetypes
You can generate new content files using the hugo new
command:
hugo new CONTENT_TYPE/FILE_NAME
Hugo will traverse the archetypes/
directory to see if any archetypes match
the value of <content_type>
. If none are found it will use default.md
to
generate the file.
If you wanted to have all of your blog posts contain specific front matter
separate from other content types, and assuming your blog posts live at
content/blog/
, you would define a blog.md
archetype with the desired front
matter defined. Then create a new blog post from the command line:
hugo new blog/a-new-post.md
A file at content/blog/a-new-post.md
will be created that contains all
pre-defined front matter.
Why would you want to do this?
Maybe your blog posts have a description
parameter and a keywords
parameter
that other content types don't have. Maybe you don't want to manually create
these files by hand every single time. It comes in handy and makes your life as
a developer that much easier. Automation for the win!
Static
The static/
directory is pretty straightforward. All assets are placed here
and can then be used throughout the project.
static/
css/
styles.css
js/
index.js
Using the structure above, you can reference both files using a filepath
relative to the static/
directory such as /css/styles.css
or /js/index.js
.
Managing your assets
One of the things Hugo lacks is an asset pipeline. It doesn't have any opinions on how you should handle your css, js, images, and fonts. All that matters is that the final assets are in this folder when you want them to be usable on the site. How they get there is up to you.
I opted for a simple Gulp workflow, writing all of my css and js in a src/
directory, watching for changes, and compiling->outputting to static/
. I'll be
writing a post on specifics soon as well.
Wrapping up
I hope this post has proved useful to you on your quest to master Hugo. It is still a very new tool for me, but I've been digging my initial experience. The directory itself is minimal and that's one of the things I enjoy about it. The less mental overhead the better, right?
You can do some pretty awesome things once you understand how these core directories are working with each other. There are projects that exist such as Victor Hugo that take Hugo's architecture to the next level. This is a project you'll definitely want to check out if you are interested in an advanced workflow.
I'll continue to post regularly about some tips and tricks I learn along the way. If you have any questions or want to correct a mistake in this post, or even if you just want to talk shop, feel free to reach out to me via Twitter!