Local preview
Two commands cover the daily work: hugo server previews changes locally, and
hugo produces a public/ deployable to any static host. The prerequisite is
Hugo Extended (0.160.1 or newer) on the machine, plus Go
when the theme comes in as a Hugo Module. The build depends on no Node.js, npm
or PostCSS — those serve only this repository’s own regression checks.
The preview server
From the site root (the directory holding hugo.yml):
Open http://localhost:1313/. Saving a file rebuilds and refreshes the browser, and switching Git branches triggers a rebuild too. The first start is slower: with the theme as a Hugo Module, Hugo has to download the module through Go into its cache, and every start after that reads the cache.
The switches worth knowing
The combination used while developing this site:
-DFE is shorthand for -D -F -E, building drafts, future and expired pages
together so a newly created page is visible while writing.
A change that did not take effect
Hugo enables fast render by default, rebuilding only what it judges affected.
When editing layouts, configuration, data/, or a file pulled in by include,
that judgement can miss, and the page appears unchanged. Three steps:
- Restart with
--disableFastRenderand see whether it comes back. - Hard-refresh the browser (
Cmd/Ctrl+Shift+R) to rule out browser cache. - If it still does not, clear the caches and restart.
Reaching it from another device
hugo server listens on 127.0.0.1 only, so no other device can reach it. To
preview on a phone or another machine:
--baseURL must be an address the other device can reach, or the page opens
while CSS and the search index — anything using an absolute path — point at
localhost.
Production build
Build deployable output with hugo, not hugo server:
The output goes to public/, which can be deployed independently of the source
tree. Each of the four switches does one thing:
--panicOnWarning deserves its own note. Most of OINK’s degradation paths warn
rather than error: a missing required giscus key, an unsupported
params.comments.type, a configuration key Hugo has deprecated — each prints one
WARNING and moves on. CI logs are rarely read line by line, so those reach
production. Putting this switch in the build command makes zero warnings the
condition for a passing build.
This site’s CI build step (.github/workflows/pages.yml) is
hugo --cleanDestinationDir --gc --minify --environment production --printPathWarnings --panicOnWarning, so any warning stops the deployment at
the build stage.
baseURL and the build environment
baseURL lives in hugo.yml and can be overridden on the command line:
When deploying to a subpath, --baseURL must include that path segment; the
details are in Deploy.
The build environment is chosen with -e / --environment; hugo defaults to
production and hugo server to development. That choice has three visible
consequences in OINK:
- Only
productionemits<meta name="robots" content="index, follow">; other environments emitnoindex, nofollow. - Under
productionrobots.txtisAllow: /; elsewhere it isDisallow: /. - Only
productionrenders Hugo’s Google Analytics template, and only there are static assets fingerprinted with SRI.
Build preview deployments (PR previews, staging) with a non-production environment, and the output declines indexing and analytics by itself:
Previewing in a container
A container is not required. Two situations suit one: a team that needs a pinned toolchain version, or one that would rather not install Hugo on every developer machine.
Go is in the image because Hugo needs it to resolve and download the module when the theme comes in as a Hugo Module. A site using a submodule, an offline archive or a plain clone can drop Go, and the image gets much smaller.
public/A container process is root by default, the generated public/ belongs to
root, and the host cannot delete it. In a shared environment, map the user ID
with --user "$(id -u):$(id -g)" (the production build command above already
does).
The image needs no Node.js, npm or PostCSS, and should have no step fetching remote browser assets. A network-isolated environment needs the base image and those two packages mirrored in advance.
Clearing caches
Hugo’s intermediate output lives in three places; clear them lightest first:
Both public/ and resources/ belong in .gitignore; generated output is
never committed.
Working on the theme alongside
This section applies only when changing the theme and the site together. Point
the module at a local checkout temporarily with HUGO_MODULE_REPLACEMENTS,
leaving go.mod untouched:
This site’s Makefile wraps those commands and expects the theme checkout at
the sibling ../oink:
Whether through the environment variable or a Go workspace
(go work init plus HUGO_MODULE_WORKSPACE=go.work), CI and production builds
read only go.mod; go.work records a developer machine’s paths and is never
committed. To judge whether a release tag works, drop the replacement and build
once against the version in go.mod.
Verifying an offline build
Acceptance in a network-isolated environment has to cover both the build stage and the browser stage. Six steps:
- Start from a verified theme archive and an empty module cache (
hugo mod clean --all). - Block outbound HTTP, HTTPS and the Go module proxy.
- Run the production build
hugo --gc --minify --printPathWarnings --panicOnWarning. - Browse pages in both languages: a documentation page, a blog page, the home page, the 404.
- Exercise search, the light/dark toggle, diagrams and content components.
- Check subresource origins and confirm there is no unexpected remote host.
The last step uses a script from the theme repository that does not depend on the site’s test framework:
The script scans every href / src / srcset / poster and form action in
all four outputs, requiring each to be a site-relative path or http /
https / mailto / tel, and rejecting inline on* handlers and
javascript: URLs. An <iframe>, <script>, <link>, <img>, <video>,
<audio>, <embed>, <object> or <source> pointing at another host is an
error; where a site genuinely embeds third-party content, --third-party
permits it, and a multi-domain language configuration adds first-party hosts
with --allow-host.
One pass proves that commit in that environment. Run it again for every theme candidate and after every bundled-dependency update.
Verify
A clean production build should look like this:
It passes on Total in … with no ERROR and no WARNING. Then confirm:
- The log has no npm, PostCSS, Autoprefixer or browser-asset download step. One appearing means upstream Docsy’s process has crept into the configuration.
public/hassitemap.xmlandrobots.txt, androbots.txtreadsAllow: /.- On a site with local search,
public/hasoffline-search-index.<language>.jsonat its root. - Open representative pages with
hugo server: one documentation page, one blog page, the home page and the 404, in both languages and both colour schemes.
For a failing build or a wrong result, see Troubleshooting.
Related
- Deploy — getting
public/to GitHub Pages, Cloudflare or elsewhere - Troubleshooting — the four common fault classes: build, language, search, platform
- From scratch and other install methods — weighing Hugo Module, submodule and offline archive
- Configuration — every key in
hugo.yml