Adding Multilingual Support to a Hugo Site
Summary: A summary of how to configure baseURL, sitemap, robots, JSON-LD, Open Graph, and meta description in a Hugo multilingual blog, and how to prevent duplicate-content issues with hreflang and canonical. Includes slug/translationKey troubleshooting.

Goal
Apply multilingual support and SEO optimization to a Hugo blog to maximize search engine exposure and provide the appropriate language version to multilingual users.
SEO Configuration
SEO (Search Engine Optimization) is the work of optimizing a site’s structure and metadata so that search engines like Google can better understand the site’s content and surface it in search results.
This document summarizes the SEO settings applied to the blog.
1. Absolute URL - Hugo baseURL Configuration
- File:
hugo/hugo.toml - Content:
baseURL = 'https://blog.plzhans.com' - Ensures correct absolute URLs are generated in sitemap.xml, RSS feed, Open Graph, etc.
- Sitemap (
sitemap.xml) and RSS feed (index.xml) are auto-generated by Hugo hugo server(development) automatically useslocalhost:1313, so no separate handling is needed
2. Automatic robots.txt Generation
- File:
hugo/hugo.toml - Content:
enableRobotsTXT = true robots.txtis auto-generated on Hugo build (allows all crawlers + includes the Sitemap URL)
3. Schema.org Structured Data (JSON-LD)
- File:
hugo/layouts/_default/single.html - Inserts
BlogPostingJSON-LD on post pages (type != "page") - Included fields: headline, datePublished, dateModified, author, description, mainEntityOfPage
- Enables rich snippets (author, date, etc.) in Google search results
4. og:image (Featured Image) / Open Graph
- File:
src/services/NotionExportService.mjs - During Notion sync, the first image in the content is detected and automatically added to the front matter
imagesfield - Open Graph meta tags are output via Hugo’s built-in template (
_internal/opengraph.html), usingimagesasog:image
5. meta description / Twitter Card
- File:
src/services/NotionExportService.mjs - The “Summary” property from Notion is output as the front matter
descriptionfield - Used by Hugo’s built-in opengraph/twitter_cards templates and the meta description in baseof.html
- Twitter Card meta tags are output via Hugo’s built-in template (
_internal/twitter_cards.html) - Other meta tags (author, viewport) are also provided by default from the theme
6. Canonical URL
- File:
hugo/layouts/_default/baseof.html - Overrides the theme’s (
m10c)baseof.htmlto add a<link rel="canonical">tag - Uses
.Permalinkas the canonical URL - Also includes multilingual hreflang tags (outputs
alternate+x-defaultwhen translated pages exist)
7. Google Analytics (GA4)
- Provided by default in the theme (
m10c) - Can be verified via GA integration when authenticating Google Search Console
Key Elements of Multilingual SEO
HTML lang Attribute
Specifies the page language to provide language information to search engines and screen readers.
1<html lang="ko">
link rel alternate hreflang
Informs search engines of each language’s page URL to prevent duplicate content issues.
1<link rel="alternate" hreflang="ko" href="https://blog.plzhans.com/ko/post/example/">
2<link rel="alternate" hreflang="en" href="https://blog.plzhans.com/en/post/example/">
3<link rel="alternate" hreflang="ja" href="https://blog.plzhans.com/ja/post/example/">
4<link rel="alternate" hreflang="x-default" href="https://blog.plzhans.com/ko/post/example/">
Canonical URL (Multilingual)
If each language is written as a professional translation, omitting canonical allows all language versions to be recognized as independent originals.
1<link rel="canonical" href="https://blog.plzhans.com/ko/post/example/">
Implementing Multilingual Support in Hugo
1. Verify Theme’s Multilingual Support
Check the lang attribute (themes/{theme}/layouts/_default/baseof.html)
1<!doctype html>
2<html lang=" .Site.Language.Lang ">
Check relLangURL support
Verify whether the home link preserves the language-specific URL. If unsupported, override baseof.html.
1<body>
2 <header class="app-header">
3 <a href=" .Site.Home.RelPermalink "><img class="app-header-avatar" src="..." alt="..." /></a>
2. Multilingual Configuration in hugo.toml
1# Default content language
2defaultContentLanguage = "ko"
3# Include the default language in a subdirectory too (/ko/)
4defaultContentLanguageInSubdir = true
5
6[languages]
7 [languages.ko]
8 weight = 1
9 languageName = "한국어"
10
11 [languages.en]
12 weight = 2
13 languageName = "English"
14
15 [languages.ja]
16 weight = 3
17 languageName = "日本語"
3. Add the canonical Tag
If the theme doesn’t support it, override baseof.html.
1<link rel="canonical" href=" .Permalink " />
4. Generate hreflang Tags
Set translationKey in the content file
1---
2id: "80"
3translationKey: "80"
4slug: "80-redis-dump-vs-aof"
5title: "Redis dump vs aof"
6---
Add hreflang to baseof.html (override if the theme doesn’t support it)
1<link rel="alternate" hreflang=" .Language.Lang " href=" .Permalink " />
2<link rel="alternate" hreflang="x-default" href=" .Permalink " />
Troubleshooting
URL Duplicate Conflict
In Hugo, you should use slug instead of url to set a post’s address.
Cause
- Setting
slugautomatically prepends the language prefix (/ko/,/en/, etc.) - Forcing
urlmeans Hugo does not automatically add the language code - When using
url, you must directly embed the language code in the URL itself for each language, like/ko/post/example,/en/post/example - If the same path is specified in
urlwithout a language code, posts in different languages end up with the same URL, causing a conflict
Solution
- Switch from
urltoslug - Set
defaultContentLanguageInSubdir = trueinhugo.tomlso that all languages, including the default language, have a subdirectory structure
Note
- When you only specify
slug, the language code is added automatically, but if the slug itself is written in a specific language, it must be translated for each language. It’s recommended to write slugs in English.
hreflang Not Generated Even After Adding translationKey
Cause
- The theme doesn’t support generating hreflang tags.
Solution
- Add hreflang-related code by overriding baseof.html