20.1 Installing a Theme: git submodule, Hugo Module, or Manual
Right, let’s get this theme onto your machine. This isn’t like picking a new wallpaper; it’s more like a organ transplant for your site. We have three main ways to do it, and your choice here is the first major decision that will either set you up for smooth sailing or haunt you for months. I’ll be honest: the “best” method isn’t always obvious, and the Hugo docs can make it seem like they’re all equally valid. They are not. Let’s break them down.
The git submodule method (The Classicist’s Choice)
This is the old reliable, the method that has stood the test of time. The concept is simple: you add the theme’s repository as a submodule within your site’s existing git repository. It’s like a git repository inside your git repository. This pins the theme to a specific commit, which is glorious for stability and reproducibility.
Why you’d use it: It’s straightforward, universally understood by anyone who uses git, and gives you explicit control over when you update the theme. You’re not at the mercy of automatic dependency resolution.
Why you might not: It adds a slight bit of complexity to your git workflow. Also, if the theme itself has dependencies (like Node.js packages for its assets), you’re on the hook for managing those yourself, which can get messy.
Here’s how you do it. First, navigate to the root of your Hugo site and then run the git submodule add command pointing to the theme’s repo and the directory you want it in (which is almost always themes/THEME_NAME).
# Navigate to your site's root
cd my-awesome-site
# Add the theme's repo as a submodule in the themes/ directory
git submodule add https://github.com/themefactory/super-theme.git themes/super-theme
This command does two things: it clones the theme into themes/super-theme, and it creates a hidden .gitmodules file that maps that directory to the remote repository. After this, you need to tell Hugo to use it by setting the theme directive in your config.toml (or equivalent).
# config.toml
theme = "super-theme"
The critical part everyone forgets: when someone else (or you on a new machine) clones your site’s repo, they need to --recursive to pull the submodule too.
git clone --recursive https://github.com/yourname/your-site.git
If they forget the --recursive flag, they’ll get a mysterious “themes/super-theme” directory that exists but is completely empty. Hugo will throw a fit, and they’ll be confused. This is the most common pitfall. Always document the --recursive requirement for collaborators.
The Hugo Modules method (The Futurist’s Gamble)
Hugo Modules are Hugo’s answer to Go Modules. They’re powerful, they can manage dependencies and their dependencies, and they feel very modern. They also have a steeper learning curve and can feel like using a rocket launcher to open a tin can if you just want a simple theme.
Why you’d use it: It’s the “official” future-forward way. It handles transitive dependencies automatically. If your theme relies on another library, Hugo Modules will grab it. It’s also great for when you want to mix and match components from different themes or override specific pieces at a granular level.
Why you might not: It requires Go to be installed on your system just to install the theme, which is frankly absurd for a static site generator. It adds a go.mod file to your project, which might be confusing if your project isn’t Go-based. The debugging experience when something goes wrong can be… esoteric.
First, you need to initialize your site as a Hugo Module. This is the point of no return.
hugo mod init github.com/yourusername/your-repo-name
The name here is meant to be a unique identifier, like a reverse DNS path. It doesn’t have to be a real website; it’s just a namespace. Then, you import the theme in your config file. The syntax varies, but here’s the common one.
# config.toml
theme = "super-theme"
# Or more explicitly with modules, often in config.yaml:
# module:
# imports:
# - path: github.com/themefactory/super-theme
Then, you let Hugo fetch everything it needs.
hugo mod get -u
The magic (and sometimes the horror) happens when you run hugo server. It will automatically check for updates to the theme and its dependencies. You can pin to a specific version or commit using @ syntax in the import path (e.g., github.com/themefactory/super-theme@v0.5.0), which I highly recommend unless you enjoy sudden, unexpected breakage.
The Manual method (The “I’m Just Knocking Around” Special)
This is exactly what it sounds like. You download a ZIP file of the theme or clone it manually and plop its contents into your themes/ directory. No git submodule magic, no Go modules nonsense.
Just… files.
Why you’d use it: It’s brain-dead simple. You see a theme, you download it, you’re done. Zero new concepts to learn. Perfect for a quick prototype or if you know you’ll never need to update it and you plan on heavily customizing it beyond all recognition.
Why you might not: You are now your own version control system. Want to update the theme? Go download the ZIP file again and figure out how to merge the changes with your customizations. Hope you remember what you changed! This method is a ticking time bomb for technical debt. I’ve done it. You’ve probably done it. We all regret it.
There’s no code example because it’s not a technical process. You drag and drop. The only “best practice” here is to understand you’re choosing a path of most resistance for future-you. If you do this, immediately make a note of what version of the theme you downloaded. Future-you will thank past-you, even if past-you was lazy.