Hey there, folks! I know I’m a little late to this party, but it’s time I talked about working with Hugo Modules. Sometime last year I wrote a post on How to Set Up a Hugo Site on Github Pages - with Git Submodules!, but let’s be honest - that’s a lot of work. Setting up git submodules and managing them over time can be a headache, especially for larger projects, and that kind of configuration just brings in a lot of overhead you may not want to deal with long term. For a more manageable project, I now recommend Hugo modules.

Benefits of Hugo Modules

The Hugo module system is powered by Go modules, which I don’t really know anything about. Feel free to explore that on your own. What I do know is this –

Hugo modules are:

  • quick to configure
  • less work to implement in your project repo
  • great for customizing your site’s look without needing to fork a full theme
  • an awesome way to share open-source code for Hugo

No more managing git submodules. No more using complex commit/push/pull schemes to work with your site repository. No more confusion about how to modify and build somebody’s theme to fit your specific project needs. Everything you were using git submodules for can (and probably should) be converted to using Hugo modules.

How to Configure a Hugo Module

If all you want is to include a module in your project, setup is super simple. In your project configuration file (config.toml or yaml or whatever), add the path to the module you want to import. For online resources, add a full URL. For local resources, add a filepath.

[module]
  [[module.imports]]
    path = "github.com/aormsby/hugo-shortcodes"
  [[module.imports]]
    path = "github.com/aormsby/hugo-custom-layouts"
  [[module.imports]]
    path = "github.com/aormsby/papermod-custom"
  [[module.imports]]
    path = "github.com/adityatelange/hugo-PaperMod"

A few things to take note of:

  • This example is toml. Yaml has different syntax.
  • This is just configuration. You still need to initialize Hugo modules. (More on that shortly.)
  • The order of your modules is important!

Modules on the bottom will get loaded first, so keep anything you don’t want overridden higher up in the module chain. All conflicting files during module import will use the last file loaded. If you want to override anything from a module you can use this bottom-to-top loading to your advantage. I show an example of this later on.

Next, you have to tell your project that you want to import these modules on build and how to import them. In your terminal, run the following commands in order –

> hugo mod init github.com/<your_username>/<your_project>
# initializes the hugo module system in your project (I used my repo URL here, any string seems fine)

> hugo mod get
# gets a reference to the latest version of each module, stored in 'go.mod'

> hugo mod vendor
# optional, downloads a local copy of the modules

Now when you build your site, the Hugo module system pulls data from all of the paths in your configuration file and makes a build with all of the data from the included modules. This could be extra layouts, assets, i18n data, shortcodes, etc., and it’s all included in your site without a whole bunch of extra setup.

Keep in mind that the stored module references in go.mod are references to a specific commit on a github repo, not a branch. You should be able to configure these by modifying the references (or perhaps there’s a command for it I don’t know), but maybe think before you do. If you point to a branch head, for example, every build could pull in new changes you haven’t vetted for your site. Sticking with a specific commit hash is probably better here. You can run hugo mod get -u to update references to all of your modules, but keep in mind this does not update the local ‘vendor’ copies. You have to run hugo mod vendor again for that.

A note on hugo mod vendor - So far I’ve seen two benefits of downloading local copies of your modules.

  1. You can see all of the assets you’re building from local module folders and modify them as you wish. I’ve found it nice to play around with theme settings and then save the changes in my own modules to override them. Vendorizing creates a code playground for you in this way.
  2. If you ever want to save a specific module configuration as part of a git commit, vendorizing allows you to include the modules at the state they’re in when you last confirmed a good build. It’s handy.

There’s a lot more you can configure with Hugo modules, but I never needed more than this for a simple site like mine. If you want to explore your options, I encourage you to check out Hugo’s module docs for more information.

How Modules Can Help You

As you saw before, I have four different modules I import for this site. Each has a different use case that has helped me organize my site and my assets better, and I want to share them here to give you some ideas on how you can take advantage of modules, too.

Basic Theme Customizations

I’m currently using the Paper Mod theme as the core of my layouts, but I wanted to customize a few pieces. If I was using git submodules like I used to, making customizations would have required forking the theme setting up a branch for my changes and referencing my entire theme fork as a submodule. But that’s overkill for a few small changes.

Instead of that, I created my own module called papermod-custom where I have a few custom layout files… and nothing else. Seriously, my module only has 4 files. This works because of the bottom-to-top module imports we talked about earlier. First, I import the core theme module that contains the base theme. Then my customizations are imported, and since I named my layouts the same as in the core theme module, any conflicting files are overridden with my customizations.

Boom. Customized theme with little to no effort.

My papermod-custom file hierarchy –

papermod-custom     #root
 |-assets
   |-css
     |-extended
       |-userCustom.css     # new file, additional styling
 |-layouts
   |-_default
     |-project-list.html    # new file, additional layout
   |-partials
     |-home_info.html       # name conflict, overrides core theme file
     |-social_icons.html    # name conflict, overrides core theme file

Common Site Code

Let’s say you have 10 Hugo sites that you’re managing, and you have the same layout file in each of them. It’s an awesome layout file - why wouldn’t you reuse it? Well, instead of updating it 10 times whenever you feel it needs a change, you can save it in your own Hugo module that you import into all of those projects.

Perfect example - I like my 404 page, and I always want to enable disqus comments. I can put those two layout files into my hugo-custom-layouts module and import them wherever I want. Modules make it easy to share things.

Additional Elements

I like shortcodes, and I want to add someone’s public shortcodes to my site. If I start a collection of shortcodes and make it a Hugo module - like so - I can add them to any project with a quick module import. It’s not just true of shortcodes, of course. Any kind of Hugo-supported asset can be thrown into a module for a quick addition to your project.

I hope this shows you that Hugo modules are pretty fantastic to work with and how much nicer they are than git submodules. If I’d known that a year ago, maybe I never would have written about git submodules! No matter. We’re here now, and that’s what counts! Happy coding!