Skip to main content
  1. Posts/

Build a Personal Website with Hugo and Congo

·8 mins

I recently built my personal blog using Hugo with the Congo theme. After a bit of configuration and theming, it is now live.

This post summarizes the configuration and deployment issues I encountered and their solutions, along with my understanding of Hugo & Congo’s design philosophy.

1. Configuration #

Hugo’s setup is straightforward, and most issues can be resolved quickly by following the official docs, so I’ll keep this section brief:

  1. Install Hugo locally and create a site.
  2. Add the Congo theme as a Git submodule.
  3. Configure the theme per the docs: Basic Configuration · Congo
  4. Push the site repository to GitHub.
  5. Create a project on Vercel, link it to GitHub, and set up auto-deploys.
  6. Light customization and enhancements:
    1. Add RSS to the main menu
    2. Integrate the Artalk comment system
    3. Integrate Google Analytics
    4. Git-ignore public and resources
    5. Customize the favicon

Once set up, publishing posts or changing the site is simply pushing to GitHub, and Vercel will auto-deploy. The workflow is clean and automated, letting you focus on content, and Vercel’s free tier is sufficient for a personal site.

2. Troubleshooting During Setup #

Below are the issues I hit during deployment, resolved via search and AI assistance.

Multilingual #

Change the default language code #

My native language is Chinese, and with modern LLMs it’s easy to convert posts into English for broader reach.

Hugo and Congo both have solid multilingual support, including Simplified Chinese. However, Congo uses zh-Hans as the default code for Simplified Chinese, which is a bit verbose in daily config.

To simplify, I copied themes/congo/i18n/zh-Hans.yaml to the project’s i18n/ folder and renamed it to zh.yaml (i.e., i18n/zh.yaml). This effectively switches the language code to zh, and I can edit i18n/zh.yaml to override the theme’s default Chinese translations.

How to organize bilingual content #

Hugo offers several ways to structure multilingual content. I use the simplest, most intuitive approach: the “filename convention” in the same page bundle directory, using *.LANG.md to distinguish languages.

For example, a post “My Awesome Trip”:

content/posts/my-awesome-trip/

  • index.zh.md (Chinese)
  • index.en.md (English)
  • featured.jpg (shared by both)

Make sure index.zh.md and index.en.md share the same translationKey. Hugo’s multilingual engine will handle language switching, URL prefixes, sitemap generation (including a sitemap index pointing to language-specific sitemaps), categories, tags, and more.

Fix inaccurate reading time for Chinese #

Hugo supports displaying reading time, but by default the value for Chinese can be quite off for two reasons:

  • The default speed is based on English (~200 words/min), which doesn’t fit Chinese.
  • CJK languages have different counting behavior and need explicit configuration.

Here’s how to fix it.

Step 1: Enable CJK support in Hugo #

Add CJK support in config/_default/hugo.toml:

# other config...
baseURL = "https://example.com/"
defaultContentLanguage = "zh"
hasCJKLanguage = true  # enable CJK character counting
Step 2: Set Chinese reading speed #

Configure the reading speed in config/_default/languages.zh.toml:

languageCode = "zh"
languageName = "中文"
languageDirection = "ltr"
weight = 1

title = "网站标题"

[params]
  dateFormat = "2006-01-02"
  reading_speed = 400  # Chinese reading speed: 400 chars/min
  # other params...
Step 3: Create a custom reading-time template #

Create layouts/partials/meta/reading-time.html to override the default in Congo:

{{- $readingSpeed := .Site.Language.Params.reading_speed | default 200 -}}
{{- $wordCount := .WordCount -}}
{{- $readingTime := math.Ceil (div (float $wordCount) (float $readingSpeed)) -}}
<span title="{{ i18n "article.reading_time_title" }}">
  {{- i18n "article.reading_time" (dict "Count" $readingTime) | markdownify | emojify -}}
</span>
{{- /* Trim EOF */ -}}

This template retrieves $wordCount, divides by reading_speed, rounds up, and outputs it via i18n.

Vercel Deployment #

“No layout file found” during build #

On the first deployment I encountered WARNs and ERRORs like:

WARN  found no layout file for "html" for kind "page": You should create a template file which matches Hugo Layouts Lookup Rules for this combination.
WARN  found no layout file for "html" for kind "taxonomy": You should create a template file which matches Hugo Layouts Lookup Rules for this combination.
WARN  found no layout file for "html" for kind "term": You should create a template file which matches Hugo Layouts Lookup Rules for this combination.
WARN  found no layout file for "html" for kind "section": You should create a template file which matches Hugo Layouts Lookup Rules for this combination.
ERROR render of "/404" failed: ".../themes/congo/layouts/baseof.html:1:4": execute of template failed: template: 404.html:1:4: executing "404.html" at <partial "functions/warnings.html" .Site>: error calling partial: partial "functions/warnings.html" not found

Error: error building site: render: failed to render pages: render of "/" failed: ".../themes/congo/layouts/baseof.html:1:4": execute of template failed: template: index.html:1:4: executing "index.html" at <partial "functions/warnings.html" .Site>: error calling partial: partial "functions/warnings.html" not found
Explanation #
  • No layout files are found during build.
  • The required warnings.html partial is missing.
Root cause #

The theme was added via Git Submodule. When pushing to GitHub, only a pointer to the theme is included. Vercel does not fetch submodules by default, so themes/congo is empty during build and Hugo can’t find any theme files.

Fix #

Add an environment variable in the Vercel project to fetch submodules:

  • Name: GIT_SUBMODULES_STRATEGY
  • Value: 1

Save and redeploy. Vercel will run git submodule update --init --recursive and the build will succeed.

Broken layout after deploy #

The site was accessible but the layout was broken. The browser console showed main.bundle.min.css failed to load.

Cause #
  1. baseURL in hugo.toml was https://yandong.xyz/ (no www), so generated asset URLs were absolute, e.g. https://yandong.xyz/css/main.bundle.min.css.
  2. On Vercel, the apex domain yandong.xyz redirects to www.yandong.xyz.
  3. The page loaded on www tries to fetch assets from the non-www domain, causing redirects and same-origin policy issues.
Fix #

Unify Hugo’s baseURL to https://www.yandong.xyz/ to match the final domain on Vercel. After redeploy, asset requests are same-origin and no longer redirected.

3. Thoughts on Hugo & Congo Design #

Composition over inheritance #

Hugo’s lookup order is: project layouts > theme layouts > Hugo internal. This “compositional override” replaces classical inheritance. Think in “layers”: draw custom parts at the top layer (project) to override the lower ones, without modifying or inheriting from them, enabling flexible and robust customization.

Configuration separation #

Hugo and Congo put switches and options (e.g., social links, comments) into config files instead of hard-coding in templates. Congo further breaks common config down:

  • languages.[lang].toml for i18n
  • menu.[lang].toml for menus
  • params.toml for theme appearance and params

Content-centric asset organization #

Traditional tools often separate content (.md) and assets (images) into distant content/ and static/ folders. Hugo’s Page Bundles champion: assets should live with the content that consumes them. A post and its images are a logical unit and should also be a filesystem unit, improving portability, maintainability, and enabling relative paths.

Encapsulating complexity #

For complexity like multilingual, image processing, and multi-target outputs, Hugo provides native solutions so you don’t need to wrestle with low-level details, performance, or compatibility.

4. Practical Principles #

Based on the above, here are my personal principles:

Never modify theme files directly #

Always put overrides or additions in the project’s layouts/, assets/, and static/; keep configuration in project .toml files. This decouples the project from the theme, making theme upgrades safe.

Embrace Page Bundles #

If a post includes images or other assets, create a directory for it and name the content file index.md. Reference assets via relative paths like ![…](./image.jpg) to keep the content unit (post + images) self-contained, easier to maintain, back up, and migrate.

To leverage defaults, your project should resemble:

.
├── assets
│   └── css
│       └── compiled
│           └── main.css  # generated by the build
├── config
│   └── _default
├── content
│   ├── _index.md
│   ├── projects
│   │   └── _index.md
│   └── blog
│       └── _index.md
├── layouts
│   ├── partials
│   │   └── extend-article-link.html
│   ├── projects
│   │   └── list.html
│   └── shortcodes
│       └── disclaimer.html
└── themes
    └── congo  # git submodule or manual theme install

Hope these notes and reflections help. If you run into other issues, feel free to reach out.