Home / Alt manpages / hugo-mod-npm-pack(1)

  • hugo-mod-npm-pack(1)
  • User command
  • linux

Pack Node Dependencies with hugo mod npm pack

Your Hugo theme has JavaScript needs, and three package.hugo.json files now disagree about the build tool version. hugo mod npm pack sorts that out: it collects Node dependencies declared by a Hugo project and its imported modules into one project-level package.json, without touching npm itself.

This guide targets Hugo 0.123.7 from the Ubuntu package on this machine. Allow about 15 minutes for a small project, plus time to inspect the generated JSON before running npm. The command is marked experimental, and newer Hugo releases have already changed the output layout, so do not apply the exact file expectations below to a different version without checking its own help and documentation.

1. Check Hugo and the project root

Run the command from the Hugo project root: a module-aware project with a Hugo configuration file and, normally, a go.mod. Confirm the binary and version before relying on its output:

$ command -v hugo
/usr/bin/hugo
$ hugo version
hugo v0.123.7+extended linux/amd64 BuildDate=2026-03-17T19:51:14Z VendorInfo=ubuntu:0.123.7-1ubuntu0.3+esm2
$ test -f hugo.yaml -o -f hugo.toml -o -f hugo.json && echo 'Hugo configuration found'
Hugo configuration found

If the project isn't a Hugo module yet, initialise it with your usual module workflow before trying to consolidate dependencies. Don't run pack from a parent directory: relative paths and the files it writes are resolved from whichever project it discovers first.

Checkpoint

You know the Hugo version, the project directory, and which user owns the files. This is an ordinary, unprivileged operation. Do not use sudo just because the project happens to have JavaScript dependencies.

2. Save the current manifest

On Hugo 0.123.7 the generated output is package.json in the project root, and pack can replace that file outright. Make a recoverable copy first if one already exists:

$ if test -f package.json; then cp --preserve=all package.json package.json.before-hugo-pack; fi
$ if test -f package.json; then sha256sum package.json; else echo 'no existing package.json'; fi
no existing package.json

Put the backup somewhere outside the project if the existing manifest is worth keeping; a neighbouring file makes the later restore command obvious. Treat it as temporary working state, not a substitute for version control.

Warning

Do not edit the old manifest while this command is running, and do not assume its scripts, name, version or private flag will survive. The installed command builds output from Hugo's own package template and dependency data, so inspect the diff before accepting the result.

3. Declare project dependencies in package.hugo.json

Create package.hugo.json in the project root when the project itself needs Node packages, using ordinary npm-format sections:

{
  "dependencies": {
    "alpinejs": "^3.14.0"
  },
  "devDependencies": {
    "vite": "^5.4.0"
  }
}

Imported modules can carry their own package.hugo.json files too. Hugo merges everything it finds across the dependency tree, and when the same package is declared twice, the version closer to the project wins. That's why the generated file is a project artefact, not a hand-maintained copy of every theme's wishlist.

If both package.hugo.json and package.json exist in a module root, current Hugo docs say the Hugo-specific file takes precedence, and the installed 0.123.7 command uses it as the project template too. Keep the source file small and deliberate: it's the one input you can always regenerate from.

4. Run the pack command

Run this as the unprivileged project user:

$ hugo mod npm pack

It can stay quiet when there's nothing to collect. On a project using the example template above, Hugo 0.123.7 writes a root package.json with the dependency sections plus a comments object naming where each entry came from:

{
  "comments": {
    "dependencies": {
      "alpinejs": "project"
    },
    "devDependencies": {
      "vite": "project"
    }
  },
  "dependencies": {
    "alpinejs": "^3.14.0"
  },
  "devDependencies": {
    "vite": "^5.4.0"
  }
}

Exact ordering and comments depend on your project and module graph, so don't compare the sample byte for byte. Check instead that the packages you expect are present and their version ranges are acceptable.

5. Inspect the generated JSON before npm

Validate the file as JSON and review the change. npm plays no part in the Hugo command, so this step installs nothing and touches no lockfile:

$ node -e 'JSON.parse(require("fs").readFileSync("package.json", "utf8")); console.log("valid JSON")'
valid JSON
$ git diff -- package.json package.hugo.json
$ node -e 'const p=require("./package.json"); console.log(Object.keys(p.dependencies || {}).sort().join("\n"))'
alpinejs

No Node available? Use whatever JSON parser is already approved for your environment. A successful parse only proves the manifest is syntactically readable, not that a package name or version actually exists in the registry.

For Hugo releases from 0.159.0 onwards, the official documentation describes a different workspace-oriented output under packages/hugoautogen/package.json, with the root manifest updated with a workspace entry instead. That's not the behaviour verified here. Check hugo mod npm pack --help and the matching release docs before upgrading a build script that relies on this page.

6. Install only after the review

Once the manifest is correct, use your project's normal package-manager command, usually npm install. This is what actually changes the lockfile and creates or updates node_modules, so review the resulting diff and run it in the project's usual build environment. Skip elevated privileges here too: a root-owned node_modules tree just creates a permissions headache later.

If the generated manifest turns out wrong, restore the previous file before npm ever runs:

$ if test -f package.json.before-hugo-pack; then mv -- package.json.before-hugo-pack package.json; fi
$ test -f package.json && node -e 'JSON.parse(require("fs").readFileSync("package.json", "utf8"))'

If pack created a brand-new manifest and there was no old one to restore, remove only that generated file, after confirming its path with pwd and readlink -f package.json. Never reach for a broad recursive removal command for cleanup here.

Common traps

  • Empty or missing output. Usually means you're not in the intended Hugo module, or there are simply no usable package declarations. Check pwd, go.mod, the Hugo config and the module imports.
  • An unexpected dependency version. Can come from a nearer module declaration. Inspect the generated comments object and the module graph before editing the output by hand.
  • A failed npm install is a separate problem from a successful Hugo pack. Keep the generated manifest, capture the npm error, and fix the source declaration rather than silently pinning a different package.
  • Don't treat the experimental command as a stable interchange format. Commit your source declarations and review generated files so an upgrade can be reversed.

Done means

  • Version and root confirmed. You checked the installed Hugo version and ran the command from the intended project root.
  • Old manifest protected. Existing package.json content has a recoverable backup or is tracked in version control.
  • Dependencies declared properly. Project dependencies live in package.hugo.json, not hidden in an unreviewed generated file.
  • JSON verified. The generated file parses and contains the dependency ranges you expected.
  • Review before install. You checked the diff before running npm, and know how to restore the previous manifest if needed.