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

ghariza mahavira fZcSoRYtgBo unsplash scaled
Genx Avatar
Share This:

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)

Related Posts

Genx

in

Comment

Leave a Reply

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

five × three =

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