Right, so you’ve got your modules set up. They’re fetching, your site builds. Wonderful. Now comes the part where you make sure this beautiful house of cards doesn’t collapse in six months when some dependency decides to release a wildly breaking change. Because they will. Trust me. Hugo modules use Go’s toolchain, specifically go mod, for dependency management, which is both a blessing and a curse. It’s incredibly powerful, but it has its own… let’s call them idiosyncrasies. We’re going to wrestle it into submission.

The Two States of Your go.mod

Think of your go.mod file as having two personalities. The first is the idealistic, minimalist one: it lists the modules you directly need and the minimum version it should use. This is what you get when you first hugo mod init or when you hugo mod get latest. It looks clean, almost innocent.

// go.mod (the "minimalist" state)
module github.com/yourusername/yourproject

go 1.21

require (
    github.com/gohugoio/hugo-mod-jslibs-dist/alpinejs v0.3.2
    github.com/bep/docuapi v0.5.2
)

The second state is the realist, the complete picture. This happens after you run a command like hugo mod tidy. It resolves every single transitive dependency (the dependencies of your dependencies) and pins them to an exact version. This file is your single source of truth and your best friend for reproducible builds.

// go.mod (the "realist" state - after `hugo mod tidy`)
module github.com/yourusername/yourproject

go 1.21

require (
    github.com/bep/docuapi v0.5.2
    github.com/gohugoio/hugo-mod-jslibs-dist/alpinejs v0.3.2
    github.com/gohugoio/hugo-mod-jslibs-dist/instantpage v0.1.0 // indirect
    github.com/someone/transitive-dep v1.7.4 // indirect
)

Notice the // indirect comments? Those are the guests your direct dependencies brought to the party. You didn’t invite them, but you’re still responsible for making sure they don’t break anything.

Pinning to an Exact Version (The “Do It” Method)

You don’t want latest. latest is a liar. It promises happiness but delivers breaking changes at 2 AM on a Tuesday. You want a specific, pinned version. The hugo mod get command is your key here. You can target a specific version, or even a specific commit, which is glorious for when you need to pull in a hotfix that hasn’t been tagged yet.

# Get a specific version
hugo mod get github.com/gohugoio/hugo-mod-jslibs-dist/alpinejs@v0.3.2

# Pin to a specific commit (yes, really)
hugo mod get github.com/bep/docuapi@d60d42c

# Update to the latest patch version of a v1.2.x release
hugo mod get -u github.com/them/theirmodule@v1.2

Running these commands updates your go.mod file, pinning that module to the exact version or commit hash you specified. This is non-negotiable for production. Do it.

Why hugo mod tidy is Your Janitor

You’ve added a module, removed one, or the sky looks a little cloudy. Run hugo mod tidy. This command is the janitor that cleans up after the party. It does three crucial things:

  1. It adds any missing dependencies necessary to build your project (e.g., those // indirect ones).
  2. It removes any dependencies that are no longer needed. Cruft begone!
  3. It updates your go.sum file, which is the cryptographic checksum file for every single dependency, ensuring you get the exact same bytes every time you download.

Always run hugo mod tidy after manually editing your go.mod file. Just do it. Make it a reflex. I’ll wait.

The Perils of the @none Escape Hatch

Sometimes, you get a module and everything explodes. It’s incompatible, it’s broken, it’s making your life miserable. You need to get rid of it. The nuclear option is:

hugo mod get github.com/problematic/module@none

This tells the Go toolchain to remove that requirement entirely. It’s the equivalent of “I never want to see that module again.” Use it sparingly, but don’t be afraid to use it when a dependency goes off the rails. It’s your escape hatch.

The Golden Rule: Commit go.mod and go.sum

This is not a suggestion. This is a commandment. You must commit both go.mod and go.sum to your version control. The go.mod file is your dependency manifest, and the go.sum file is your cryptographic lock. It’s the combination of these two that guarantees anyone (or any CI system) that runs hugo mod tidy will end up with the exact same dependency tree as you have on your machine. Without it, you’re just hoping. Hope is not a strategy.