Hugo Usage Guide

Applies to Hugo v0.168.0 (Extended). Commands use the macOS/Linux shell as examples. Compiled from the official Hugo documentation. Theme-specific private configuration should follow the actual theme repository.

1. Installation and Version Check

macOS (Homebrew):

brew install hugo
hugo version
hugo env

On Linux, use the corresponding distribution package or the binary officially released by Hugo. When using SCSS/Sass or certain theme asset pipelines, confirm that the installed version includes the Extended feature.

2. Creating a Project

hugo new project myblog
cd myblog
git init

Typical directory structure:

myblog/
├── archetypes/       # Templates for new articles
├── assets/           # Assets that need to go through the resource pipeline
├── content/          # Markdown content
├── data/             # Data files
├── i18n/             # Theme interface translations
├── layouts/          # Custom templates, can override theme templates
├── static/           # Copied as-is to the site root
├── themes/           # Local themes or Git submodules
├── hugo.toml         # Site configuration (YAML and other formats are also supported)
└── public/           # Build output (generated after running hugo)

Hugo itself does not include a complete blog appearance. If there is no available theme or custom layout, you may see a warning such as found no layout file for "html".

3. Using a Ready-Made Theme (Git Submodule)

The following uses the Ananke theme from Hugo’s official quick start as an example:

git submodule add https://github.com/gohugo-ananke/ananke themes/ananke

Configure in hugo.toml (do not declare the same TOML key twice):

baseURL = 'https://example.com/'
title = 'My Blog'
theme = 'ananke'

Start the preview:

hugo server -D

Different themes vary in menus, homepage, parameters, Hugo version, and asset compilation dependencies. When migrating configuration, prefer the theme’s README and example site. Do not copy another theme’s [params] block as-is.

Git Submodule Maintenance

Cloning on a new machine:

git clone --recurse-submodules <repository URL>

Filling in submodules for an existing clone:

git submodule sync --recursive
git submodule update --init --recursive

Updating submodules (will track the configured remote branch; review changes before committing):

git submodule update --remote --merge
git diff --submodule
git add .gitmodules themes/
git commit -m 'Update Hugo theme'

When migrating a submodule’s Git URL: first modify url in .gitmodules, then run git submodule sync --recursive. If the CI network is unstable, you may switch to an accessible Git mirror, but make sure the mirror has the required commit objects; you may also initialize submodules yourself after cloning the main repository.

4. Creating and Editing Articles

hugo new content posts/hello-world.md

Example article:

---
title: "Hello World"
date: 2026-10-17T12:00:00+08:00
draft: false
description: "Article summary"
tags: ["Hugo", "Blog"]
categories: ["Tech"]
---

This is the body.

## First-level section

**Bold**, *italic*, [external link](https://gohugo.io).

Key fields:

FieldPurpose
titleTitle
dateArticle time
drafttrue means draft, not included in the default production build
descriptionSummary, whether to display it depends on the theme
tags, categoriesTaxonomy, specific page display depends on the theme
slugCustom URL tail segment (the actual path is also affected by the permalink configuration)

Note: articles with a future date may also be excluded from the default production build; check the date, time zone, and --buildFuture.

Images and Static Files

Site-wide static files go in static/images/logo.png, referenced in articles as:

![Logo](/images/logo.png)

A page bundle can use:

content/posts/hello/
├── index.md
└── cover.jpg

Write ![Cover](cover.jpg) in index.md. If you want to use theme image processing, thumbnails, and other features, you still need to follow the page-resource conventions of that theme.

5. Local Development and Build

hugo server                  # Preview published content
hugo server -D               # Include drafts
hugo server --bind 0.0.0.0   # Allow other devices to connect; mind network access controls
hugo                         # Output to public/
hugo --gc --minify           # Common production build
hugo -d dist                 # Output to dist/

Hugo’s build command does not automatically clear the output directory before building. If you delete articles, change URLs, or switch languages, old files may remain. It is recommended to clear the artifacts in the CI’s temporary build directory before building, rather than blindly cleaning any path.

rm -rf public
hugo --gc --minify

Only run the above deletion when you have confirmed that public/ is purely a build artifact with no user data.

6. Multilingual Sites

The following shows the basic configuration for Chinese and English. The supported language menu and switcher depend on the theme:

baseURL = 'https://example.com/'
title = 'My Blog'
defaultContentLanguage = 'zh-cn'
defaultContentLanguageInSubdir = false

[languages]
  [languages.zh-cn]
    languageName = 'Simplified Chinese'
    languageCode = 'zh-CN'
    weight = 1
    title = '我的博客'
  [languages.en]
    languageName = 'English'
    languageCode = 'en-US'
    weight = 2
    title = 'My Blog'

One way to organize content by language directory:

content/
├── zh-cn/
│   └── posts/hello.md
└── en/
    └── posts/hello.md

Before using the above language directory layout, combine the multilingual content directory configuration or language mounts to confirm that content is correctly attributed. A simpler approach that needs no extra mounts is using translation filenames in the same directory, such as hello.zh-cn.md and hello.en.md; the default language’s URL can also be arranged per the project configuration.

Recommendation: for sites that already have content/posts/, start with the filename suffix method:

content/posts/
├── hello.zh-cn.md
└── hello.en.md

It is best to set a consistent translationKey on translated pages (only needed when the file path cannot directly associate them), to avoid the multilingual menu pointing to the wrong page.

7. Using HTML in Markdown

Hugo uses Goldmark by default. If you see Raw HTML omitted while rendering in the log, raw HTML in Markdown is not being rendered. Only enable this in hugo.toml when you trust the article content:

[markup.goldmark.renderer]
  unsafe = true

Once enabled, embedded HTML in articles will participate in the output. If the content comes from untrusted contributors, consider the risk of script injection.

8. Git and Gitea Actions Publishing

It is recommended to keep the source repository (including .gitmodules, content/, hugo.toml, etc.) separate from the static publishing repository or branch. A typical CI flow:

push to source main
       │
       ▼
Pull source and theme submodules
       │
       ▼
Install the specified Hugo version
       │
       ▼
Clean the temporary build output and run hugo --gc --minify
       │
       ▼
Commit the public/ files to the publishing repository's main or publishing branch
       │
       ▼
Caddy / Nginx serves the static directory

You can first verify these commands inside the build container:

git submodule sync --recursive
git submodule update --init --recursive
rm -rf public
hugo --gc --minify
find public -maxdepth 2 -type f | head

Gitea Actions example workflows must be configured individually based on the act_runner’s labels, container image, Git authentication, permissions, Hugo install path, and target branch. Do not assume GITHUB_SHA or custom COMMIT_SHA automatically exists in every shell step; with set -u, undefined variables will cause the build to fail directly. To get the source commit value, run inside an already-checked-out Git repository:

SOURCE_SHA="$(git rev-parse HEAD)"

Use a separate working directory for the target publishing repository, to avoid accidentally deleting source files. Check for non-fast-forward conflicts on the branch before pushing. Do not use unchecked git push --force as a general publishing strategy.

9. Common Troubleshooting

9.1 found no layout file for "html"

The cause is usually that the theme was not loaded correctly, the theme is missing the corresponding template, or the theme name in the config file does not match the themes/ directory.

ls -la themes/
cat .gitmodules
hugo config | grep -i theme
hugo --logLevel debug

Confirm that template files such as layouts/ actually exist inside the theme directory, and check the theme’s own usage notes. Do not silence a blank-site warning.

9.2 Raw HTML omitted

Check markup.goldmark.renderer.unsafe as in Section 7. You can also try to replace raw HTML with Markdown or Hugo shortcodes.

9.3 Git Submodule Directory Is Empty

git submodule status --recursive
git submodule update --init --recursive

If a network error is reported, check the URL, authentication, and DNS/proxy inside the build container in .gitmodules, not only the host network.

9.4 Page Not Published

Check the article’s draft, date, expiration time, language, and the target theme’s routing. Check:

hugo list all
hugo list drafts
hugo list future
hugo list published

9.5 CI Build Succeeds But Published Content Does Not Change

Check whether public/ contains the expected HTML, whether the publishing branch was updated, whether the web root points to the latest deployment location, and whether the browser/CDN is caching old content. Do not assume it is a Hugo caching problem first.

# 1. Pull updates (including submodules)
git pull --ff-only
git submodule update --init --recursive

# 2. Create a new article
hugo new content posts/new-post.md

# 3. Edit and preview locally
hugo server -D

# 4. Change draft to false in the article's front matter

# 5. Commit and push (CI builds/publishes automatically)
git add content/
git commit -m 'Add new post'
git push

11. References