If you run a multilingual Hugo site with custom content types, you likely want flexible sitemaps:
- Easy to read for humans (via XSL)
- Split per language (/sitemap.xml, /ja/sitemap.xml, etc.)
- Split per custom section (for example, separate sitemaps for posts and tech)
Below is a practical, reusable solution.
1. Add XSL Stylesheets for Human-Readable Sitemaps
Browsers show plain XML sitemaps as raw code. But with XSL stylesheets, you can style them for clearer navigation.
- Save these files in your Hugo static directory:
static/sitemap.xsl (for section sitemaps):
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:sitemap="http://www.sitemaps.org/schemas/sitemap/0.9">
<xsl:template match="/">
<html>
<head>
<title>Sitemap</title>
<style type="text/css">
body { font-family: sans-serif; background: #fafafa; }
ul { line-height: 2; list-style: disc inside; }
</style>
</head>
<body>
<h2>Sitemap Links</h2>
<ul>
<xsl:for-each select="sitemap:urlset/sitemap:url">
<li>
<a href="{sitemap:loc}"><xsl:value-of select="sitemap:loc"/></a>
<xsl:if test="sitemap:lastmod">
<small> (Last modified: <xsl:value-of select="sitemap:lastmod"/>)</small>
</xsl:if>
</li>
</xsl:for-each>
</ul>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
static/sitemapindex.xsl (for sitemap indexes):
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:sitemap="http://www.sitemaps.org/schemas/sitemap/0.9">
<xsl:template match="/">
<html>
<head>
<title>Sitemap Index</title>
<style type="text/css">
body { font-family: sans-serif; background: #fafafa; }
ul { line-height: 2; list-style: disc inside; }
</style>
</head>
<body>
<h2>Sitemap Index</h2>
<ul>
<xsl:for-each select="sitemap:sitemapindex/sitemap:sitemap">
<li>
<a href="{sitemap:loc}"><xsl:value-of select="sitemap:loc"/></a>
<xsl:if test="sitemap:lastmod">
<small> (Last modified: <xsl:value-of select="sitemap:lastmod"/>)</small>
</xsl:if>
</li>
</xsl:for-each>
</ul>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
2. Configure hugo.toml for Multilingual and Custom Output Formats
Add (or complete) these sections in your hugo.toml:
textdefaultContentLanguage = "en"
[languages]
[languages.en]
languageName = "English"
weight = 1
[languages.ja]
languageName = "Japanese"
weight = 2
[permalinks]
posts = "/:slug/"
[outputs]
home = ["HTML", "RSS", "SITEMAP"]
section = ["HTML", "RSS", "SITEMAP"]
[outputFormats]
[outputFormats.SITEMAP]
mediatype = "application/xml"
baseName = "sitemap"
isPlainText = true
rel = "sitemap"
This tells Hugo to generate sitemap.xml files per section and per language.
3. Create the Sitemap Index at the Root Level
In layouts/_default/sitemap.xml:
{{ printf "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\" ?>" | safeHTML }}
<?xml-stylesheet type="text/xsl" href="/sitemapindex.xsl"?>
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
{{ range .Site.Sections }}
<sitemap>
<loc>{{ .Permalink }}sitemap.xml</loc>
{{ with .Lastmod }}
<lastmod>{{ .Format "2006-01-02T15:04:05Z07:00" }}</lastmod>
{{ end }}
</sitemap>
{{ end }}
</sitemapindex>
This file links to each section (like /posts/sitemap.xml, /tech/sitemap.xml), which we’ll generate soon.
4. Generate Human-Readable Section Sitemaps
For each section, create a sitemap.xml file in its layout.
For Posts
Create layouts/posts/sitemap.xml:
{{ printf "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\" ?>" | safeHTML }}
<?xml-stylesheet type="text/xsl" href="/sitemap.xsl"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
{{ range (where .Site.Pages ".Section" "posts") }}
<url>
<loc>{{ .Permalink }}</loc>{{ if not .Lastmod.IsZero }}
<lastmod>{{ safeHTML ( .Lastmod.Format "2006-01-02T15:04:05-07:00" ) }}</lastmod>{{ end }}{{ with .Sitemap.ChangeFreq }}
<changefreq>{{ . }}</changefreq>{{ end }}{{ if ge .Sitemap.Priority 0.0 }}
<priority>{{ .Sitemap.Priority }}</priority>{{ end }}
</url>
{{ end }}
</urlset>
For Custom Post Type (e.g., Tech)
Create layouts/tech/sitemap.xml:
{{ printf "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\" ?>" | safeHTML }}
<?xml-stylesheet type="text/xsl" href="/sitemap.xsl"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
{{ range (where .Site.Pages ".Section" "tech") }}
<url>
<loc>{{ .Permalink }}</loc>{{ if not .Lastmod.IsZero }}
<lastmod>{{ safeHTML ( .Lastmod.Format "2006-01-02T15:04:05-07:00" ) }}</lastmod>{{ end }}{{ with .Sitemap.ChangeFreq }}
<changefreq>{{ . }}</changefreq>{{ end }}{{ if ge .Sitemap.Priority 0.0 }}
<priority>{{ .Sitemap.Priority }}</priority>{{ end }}
</url>
{{ end }}
</urlset>
Repeat for any other custom section (layouts/[section]/sitemap.xml).
5. Hugo Handles the Language Magic
Hugo will generate sitemaps for each language automatically
(e.g., http://localhost:1313/sitemap.xml, http://localhost:1313/ja/sitemap.xml).
If you want different sitemap contents per language, use conditions like .Lang in your templates.
6. Access Your Human-Readable, Multilingual Sitemaps
- Index:
- http://localhost:1313/sitemap.xml
http://localhost:1313/ja/sitemap.xml
- Per section/language:
- http://localhost:1313/posts/sitemap.xml
http://localhost:1313/tech/sitemap.xml
http://localhost:1313/ja/posts/sitemap.xml
http://localhost:1313/ja/tech/sitemap.xml
Open these URLs in the browser, and thanks to XSL, they’re beautifully formatted for humans.
Optional Enhancements
- Add titles/descriptions per link — append info to <li> in the XSL if desired.
- Add language selectors in XSL to jump between languages.
Final Tip
Whenever you add more content types (sections) or languages, simply add the corresponding layout to generate a separate sitemap.
Conclusion
This method gives you:
- Human readable, browsable sitemaps (helpful for editors and SEO audits)
- Clean separation by content type and by language (ideal for internationalized sites)
Leave a Reply