How to Create Human-Readable, Language-Aware, and Custom Post-type Sitemaps in Hugo

ghariza mahavira fZcSoRYtgBo unsplash scaled

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)

Genx

in

About The Author

Genx

Born in 1982 in Japan, he is a Japanese beatmaker and music producer who produces experimental hiphop beats. He is the owner of Genx Records. Because he grew up internationally, he understands English. His hobbies are muscle training, artwork creation, website customization, and web3. He also loves Korea.

Website: genxrecords.xyz

Share This Post:


Comment

Leave a Reply

Your email address will not be published. Required fields are marked *

sixteen − eight =

Webmention: Have you posted a response to this article? Let me know the URL: