Building static sites with Hugo is fast and fun!

I’ve built out a few sites and am constantly delighted by the new features that are introduced into Hugo.

One of these features is “mounts”.

What’s a Mount and What Does it Do?

Think of this as a part of Hugo modules.

Modules plug into your project to replace or add components in the standard Hugo directories:

  • static
  • content
  • layouts
  • data
  • assets
  • i18n
  • archetypes

As part of this, a Hugo mount simply allows you to mount directories that are external to your project into your project.

Let’s say your Hugo project is in:

/websites/some-cool-site/

And you have a content directory with a bunch of markdown files in:

/content/articles/

You can mount your /content/articles/ directory into your Hugo project.

This can allow you to version control content and website files separately, or do more complicated things like programmatically generate content files from an API and build your site with those files (I’m doing this!).

The Best Feature of Mounts Is…

What makes mounts even better is that you can mount more than one directory into the same “virtual” Hugo directory.

This means you can pull different Javascript libraries into a shared assets folder or build content from multiple directories.

Want to grab favicon assets from your full stack web application to use on your app documentation site? You can do that. Use the same terms and conditions content across different websites? You can do that.

So many applications for mounts!

How To Use Mounts

To take advantage of Hugo modules, you should start by initializing your site as a Go module. This is as easy as running:

hugo mod init your-project-name

Then, take a look at your existing mounts with this command:

hugo config mounts

You’ll notice that these standard Hugo directories:

  • static
  • content
  • layouts
  • data
  • assets
  • i18n
  • archetypes

…are already configured as mounts!

From there, setting up your own mounts is easy. In your hugo.toml file, add a section that looks like this:

[module]
  [[module.mounts]]
    source = "content"
    target = "content"
  [[module.mounts]]
    source = "/some/external/content"
    target = "content"
  [[module.mounts]]
    source = "assets"
    target = "assets"
  [[module.mounts]]
    source = "/some/externally/generated/file.json"
    target = "assets/js/file.json"
  [[module.mounts]]
    source = "/some/commonly/used/script.js"
    target = "assets/js/script.js"
  [[module.mounts]]
    source = "/some/external/static"
    target = "static"

If you run hugo config mounts again, you’ll see these new entries alongside the old entries.

Notice Three Things:

First, the standard content and assets directory are specified, but the standard static directory is not.

This means that you can use Hugo’s content and assets directories within your project, but static files will only be pulled from the external directory.

If you decide to use the Hugo static directory later, simply add it to the config. You can do this with any of Hugo’s standard directories.

Second, you can mount directories or individual files. Want to use a script from another project? Go for it!

Third, multiple directories are mounted into the same standard Hugo directory. Content comes from two different directories and assets come from three locations. Hugo melds it all together into one unified directory.

Remember Your Mountings!!!

The biggest problem with mounts is remembering that you’ve used them!

If you’ve stepped away from a project for a while, you might forget that a particular file comes from an external source.

This sounds silly, but I’ve had a few minutes trouble determining where an imported Javascript file was coming from before remembering it came from a mount!

Consider making a troubleshooting section in a readme that lists your mounts or reminds you to take a look at your Hugo configuration.

How I Recently Used Mounts

I just built a data-heavy site with parametric search.

There are two types of content used in this site:

  • Machine built data pages
  • Hand-written articles

Machine built data pages

All of the data pages are output into an export directory that is external to the Hugo project. This directory is emptied and recreated during site builds as more data is added.

So, the machine built data pages look like this:

export
└── content
    ├── item-1
    │   ├── document.pdf
    │   ├── image.webp
    │   └── index.md
    ├── item-2
    │   ├── document.pdf
    │   ├── image.webp
    │   └── index.md
    └── item-3
        ├── document.pdf
        ├── image.webp
        └── index.md

Hugo takes item-1, item-2, and item-3 and builds webpages based on the templates I designed.

A little nitty-gritty

This isn’t necessary for understanding Hugo mounts!

But this is a real world use case for building what goes into the folders used in a mount.

Data for the site comes from a Django API I built. The API is not exposed to the world, so the workflow looks like this:

  1. Create an export folder
  2. Create subfolders that match items (which correspond to a website page)
  3. Build JSON data for each item locally and copy it to the matching subfolder
  4. Copy images and documentation to the matching subfolder

As this site contains a parametric search, JSON data is also compiled for an Alpine JS component that allows filtering.

Hand-written articles

I realized that I also wanted regular, old, handwritten articles to be incorporated into the site.

This time I used the standard Hugo content directory to write and store my content files. This looks something like:

/projects/hugo-project/content # This is the standard Hugo content directory.
├── about
│   ├── image.webp
│   └── index.md
├── article
│   ├── image.webp
│   └── index.md
├── privacy
│   └── index.md
├── terms
│   └── index.md
└── _index.md

To use the machine-generated content with hand-written content, I added this to my hugo.toml file:

[module]
  [[module.mounts]]
    source = "content" # Hand-written: Standard Hugo content directory
    target = "content"
  [[module.mounts]]
    source = "/export/content" # Machine-generated: External directory
    target = "content"

This One Small Feature…

…makes it easy to build heavily data-driven sites, use icons from multiple different providers, have unified styles on different websites, and just gives you a lot more flexibility!

Hope you enjoy, and look for a few more posts coming up on Hugo features.