How to add a theme using modules (for beginners)

It took me longer than I expected to figure out how to add a theme to a site the new way: using modules. It is very easy — much easier than the two other methods I’d used to add themes — once you know what you need to do. Here’s how it worked, in three short steps.

First, at the command line, go to the top level of your project. Initialize your site as a hugo module. To do that, type hugo mod init and then the url where the site will be deployed (without a trailing slash at the end). For example:

hugo mod init yourname.github.io/sitename

for GitHub Pages user yourname deploying sitename.

Second, add the theme as a module in your config file. Here is how it looks to load the hugo-xmin theme in a config file which uses the default toml format:

[module]

  [[module.imports]]
    path = "github.com/yihui/hugo-xmin"
    disable=false

The [module] indicates that this is a section of the config file for modules, and then the path is just the url fetched from GitHub for the theme I wanted to add. A second theme could even be added below, in the same format, especially if one of them is marked disable=true. That even allows quick switching between themes!

Third, tell hugo to use this theme. That looks like adding the following to your config.toml file, where hugo-xmin is the name of the theme:

theme = ["hugo-xmin"]

Note that this command will also look at a series of theme items or other code, prioritizing the ones on the left, so the following would load the hugo-xmin theme, adding to it (and overriding it) with anything in the my-shortcodes module:

theme = ["my-shortcodes", "hugo-xmin"]

To use that module of course we would have had to load it, with something like

  [[module.imports]]
    path = "github.com/yourname/my-shortcodes"
    disable=false

And you now have a theme loaded with your extras.

(Corrections and improvements from more advanced users welcome!)

14 Likes

Great. One little correction:

This:

[module]
  [[module.imports]]
    path = "github.com/yihui/hugo-xmin"
  [[module.imports]]
    path = "github.com/yourname/my-shortcodes"

Is the same as:

theme = ["github.com/yihui/hugo-xmin",  "github.com/yourname/my-shortcodes" ]

You don’t need both.

9 Likes

Thanks all for those informations. Since this post is now closed I hijack this thread to give some more hints.

After not succeeding to use modules even after reading those great post and the doc, even if I’m pretty comfortable with Hugo, I finally got some aha! moment when I realised that you need to understand somethings the Golang users know for a long time, but hidden for us poor mortals only using Hugo.

Do not hesitate to correct me if something is wrong or not really accurate. Here is the 2 simple things:

The path name module.

  • If you want to create/develop a module, you create your git folder module1-hugo as usual.
  • But the name module you give cannot be a local name (aka module1-hugo).
  • It has to be with a domain name as prefix (aka github.com/divinerites/module1-hugo)
  • So now you can initiate the Hugo module with the command : hugo mod init github.com/divinerites/module1-hugo, and reference it later in your path directive.

The version numbers

  • Unless you’ll give a (semantic) tag version number (aka v1.0.3) to your git commit, trying to use modules will fail with diverses error messages.

If you follow those 2 basic (for Go & Go modules users) recommandations, then using Hugo Modules is easy and so powerful (thanks Bep for this fantastic implementation).

May be just 2 other things, already in those great tutorials, but better say that again here:

  • A useful basic commands to update only one module from your internet repository : hugo mod get -u github.com/divinerites/module1-hugo
  • When you want to update/develop locally the beauty of modules is that you just add a replace directive for the module in your project go.mod, and bim bam boum, the local module folder is in use, with a fresh Hugo reload in millisecond. No need to do the merge/push/tag/update dance. Brilliant.

replace github.com/divinerites/module1-hugo => /Users/mycomputername/Documents/Git/module1-hugo

3 Likes

Your description is spot-on and very good. I just want to add this:

  • It’s perfectly fine to not use tags at all, and I find that it doing hugo mod get -u will do what you expect in that situation (get the latest commit).
  • You can append the version number or Git sha ref when doing “getting”, e.g. github.com/yourname/my-shortcodes@v1.0.0. You can also edit the go.mod file and then running Hugo.
  • You can also have versioned mono-repos, see Tags · bep/hugo-jslibs · GitHub
2 Likes

Super cool. Thanks.

But you will then lose out on the version selection in use:

1 Like

And how can I add the exampleSite?

I would copy the contents of that folder to my own content folder. It’s a sample site, so I will probably change it and adding it as mount makes no sense then.

There is something I just found out after developing a website and theme with Hugo for a week.

I have no background in Golang, but coming from Git, I thought the Hugo module init was similar to the the git submodule init. It’s not.

Let’s say you have 2 repositories : the imported and the importer.

In Git, your run

$ git submodule add https://github.com/USER/imported
$ git submodule init

from within the importer repo.

In Hugo, you run hugo mod init https://github.com/USER/imported from within the imported repo. This creates a sort of header to the imported repo that makes it self-aware of its own URL and importable later. Then, in your importer repo, you link the imported repo with the config params:

[module]
  [[module.imports]]
    path = "github.com/USER/imported"
    disable = false

The Hugo doc and even this post talk about “initializing a project” without mentionning which one (imported or importer) it is. The Hugo logic implies you have control over the imported repo to initialize as a module, while the Git logic implies nothing but doesn’t mount as subfolders as nicely.

1 Like