Skip to content
← All writing
November 21, 2025 · 9 min read

Hugo Mastery Part 3: Modern Features & Advanced Capabilities

Master Hugo's modern features including YAML anchors, HTML-to-Markdown conversion, efficient random sampling, and advanced configuration patterns. - hugo - updates - features - modern-web - static-site-generator - technology - web-development

Introduction #

Modern Hugo has introduced several powerful features that address common development challenges: configuration duplication, inefficient content sampling, and complex content migrations. This guide covers the essential capabilities that separate basic Hugo sites from professionally architected ones.

You’ll learn about YAML anchors for DRY configuration, the collections.D function for efficient random sampling, transform.HTMLToMarkdown for content conversion, and module version control for better dependency management. Each feature solves specific production problems and scales with your project’s complexity.

Essential Modern Features #

1. YAML Anchors for DRY Configuration #

YAML anchors and aliases eliminate configuration duplication by allowing you to define settings once and reference them throughout your configuration. This is particularly valuable for multi-environment setups where most settings remain constant across development, staging, and production.

Basic Syntax #

Before (repetitive):

production:
  author: John Doe
  language: en
  theme: modern
  baseURL: https://example.com
 
development:
  author: John Doe
  language: en
  theme: modern
  baseURL: http://localhost:1313

After (DRY with anchors):

defaults: &defaults
  author: John Doe
  language: en
  theme: modern
 
production:
  <<: *defaults
  baseURL: https://example.com
 
development:
  <<: *defaults
  baseURL: http://localhost:1313

Real-World Examples #

Menu Configuration:

menu_defaults: &menu_defaults
 class: nav-item
 target: _self
 
menu:
 main:
 - <<: *menu_defaults
 name: Home
 url: /
 weight: 1
 
 - <<: *menu_defaults
 name: Blog
 url: /blog/
 weight: 2
 
 - <<: *menu_defaults
 name: About
 url: /about/
 weight: 3

Multilingual Setup:

lang_defaults: &lang_defaults
  disabled: false
  params:
  dateFormat: "2 January 2006"
  copyright: © 2025-2026 My Site
 
languages:
  en:
  <<: *lang_defaults
  languageName: English
  weight: 1
 
  de:
  <<: *lang_defaults
  languageName: Deutsch
  weight: 2
  params:
  dateFormat: "2. January 2006"
 
  fr:
  <<: *lang_defaults
  languageName: Français
  weight: 3

Environment-Specific Settings:

base_settings: &base
  buildDrafts: false
  buildFuture: false
  buildExpired: false
 
prod_settings: &prod
  <<: *base
  minify: true
  environment: production
  googleAnalytics: UA-XXXXX-Y
 
dev_settings: &dev
  <<: *base
  buildDrafts: true
  buildFuture: true
  environment: development
 
# Use in build configuration
build:
  <<: *prod # Switch to *dev for development

Breaking Change: Boolean Handling #

Important Migration Required:

YAML 1.1 boolean strings are no longer auto-converted.

Old Behavior:

published: yes # Converted to true
draft: no # Converted to false
enabled: on # Converted to true
disabled: off # Converted to false

New Behavior:

published: true # Use explicit boolean
draft: false # Use explicit boolean
enabled: true # Use explicit boolean
 
# Or keep as strings
status: "yes" # String value "yes"

Migration Script:

# Search for old-style booleans
grep -r ": yes$\|: no$\|: on$\|: off$" config/
grep -r ": yes$\|: no$\|: on$\|: off$" content/
 
# Replace with proper booleans
# `: yes` → `: true`
# `: no` → `: false`
# `: on` → `: true`
# `: off` → `: false`

2. HTML to Markdown Conversion #

The transform.HTMLToMarkdown function provides native HTML-to-Markdown conversion, essential for content migrations, generating LLM-friendly output, and creating plain text versions of rendered content. This eliminates the need for external conversion tools or custom regex solutions.

Basic Usage:

{{ $html := "<h1>Welcome</h1><p>This is <strong>bold</strong> text.</p>" }}
{{ $markdown := transform.HTMLToMarkdown $html }}
{{ $markdown }}
<! - Output: # Welcome\n\nThis is **bold** text. - >

Convert Page Content:

<! - Convert current page HTML to Markdown - >
{{ $markdown := transform.HTMLToMarkdown .Content }}
 
<! - Use for API endpoints or exports - >
{{ $markdown | safeHTML }}

Real-World Use Case: LLM-Friendly API:

<! - layouts/_default/llm.txt - >
{{- range .Site.RegularPages -}}
Title: {{ .Title }}
URL: {{ .Permalink }}
Date: {{ .Date.Format "2006-01-02" }}
 
Content:
{{ transform.HTMLToMarkdown .Content }}
 
---
 
{{ end }}

Use Case: Content Export:

<! - layouts/_default/export.md - >
{{- $pages := where .Site.RegularPages "Type" "posts" -}}
{{- range $pages -}}
# {{ .Title }}
 
{{ transform.HTMLToMarkdown .Content }}
 
---
 
{{ end }}

3. Efficient Random Sampling with collections.D #

The collections.D function implements Vitter’s Method D for sequential random sampling, providing O(n) performance for selecting random items from large collections. This is significantly more efficient than shuffling entire collections when you only need a small sample.

For sites with hundreds or thousands of pages, this reduces build time and memory usage compared to traditional shuffle-and-slice approaches.

Syntax:

{{ collections.D $sampleSize $populationSize }}

Random Posts Sidebar:

<! - Select 5 random posts - >
{{ $allPosts := where .Site.RegularPages "Type" "posts" }}
{{ $indices := collections.D 5 (len $allPosts) }}
 
<aside class="random-posts">
 <h3>You Might Also Like</h3>
 {{ range $indices }}
 {{ $post := index $allPosts . }}
 <article>
 <h4><a href="{{ $post.Permalink }}">{{ $post.Title }}</a></h4>
 <p>{{ $post.Summary }}</p>
 </article>
 {{ end }}
</aside>

Random Image Gallery:

{{ $images := .Resources.Match "gallery/*" }}
{{ $totalImages := len $images }}
{{ $displayCount := 6 }}
 
{{ $randomIndices := collections.D $displayCount $totalImages }}
 
<div class="gallery">
 {{ range $randomIndices }}
 {{ $img := index $images . }}
 <img src="{{ $img.RelPermalink }}" alt="{{ $img.Name }}">
 {{ end }}
</div>

Random Testimonials:

{{ $testimonials := .Site.Data.testimonials }}
{{ $count := len $testimonials }}
{{ $randomIndices := collections.D 3 $count }}
 
<section class="testimonials">
 {{ range $randomIndices }}
 {{ $testimonial := index $testimonials . }}
 <blockquote>
 <p>{{ $testimonial.quote }}</p>
 <cite>{{ $testimonial.author }}</cite>
 </blockquote>
 {{ end }}
</section>

Performance Note: Method D is more efficient than shuffling the entire collection, especially with large datasets.

4. Module Version Control #

Specify module versions directly in your Hugo configuration for better dependency management.

Before:

# Required manual go.mod editing or hugo mod get
hugo mod get github.com/user/theme@v1.2.3

After:

[[module.imports]]
 path = "github.com/user/theme"
 version = "v1.2.3"

Multi-Version Documentation:

# Maintain multiple API versions
[[module.imports]]
 path = "github.com/mycompany/api-docs"
 version = "v3.0.0"
 [[module.imports.mounts]]
 source = "content"
 target = "content/docs/v3"
 
[[module.imports]]
 path = "github.com/mycompany/api-docs"
 version = "v2.1.0"
 [[module.imports.mounts]]
 source = "content"
 target = "content/docs/v2"
 
[[module.imports]]
 path = "github.com/mycompany/api-docs"
 version = "v1.5.0"
 [[module.imports.mounts]]
 source = "content"
 target = "content/docs/v1"

Development vs Production:

# config/production/config.toml
[[module.imports]]
 path = "github.com/user/theme"
 version = "v2.1.0" # Stable version
 
# config/development/config.toml
[[module.imports]]
 path = "github.com/user/theme"
 version = "main" # Latest development

Customize your URLs with powerful new permalink tokens.

New Tokens:

  • :sectionslug - Section slug for current page
  • :sectionslugs - All section slugs in path

Multilingual Example:

[permalinks]
 posts = "/:year/:sectionslug/:slug/"
 
# Results:
# English: /2025/blog/my-post/
# German: /2025/nachrichten/mein-beitrag/

Nested Sections:

[permalinks]
 docs = "/:sectionslugs/:slug/"
 
# Results:
# /docs/getting-started/installation/
# /docs/advanced/optimization/

6. Enhanced Footnote Control #

Gain precise control over how footnotes render in your content.

Configuration:

[markup.goldmark.extensions.footnote]
 returnLinkContents = ""
 autoPrefix = true # Auto-prefix footnote IDs to prevent conflicts

Custom Backlink HTML:

[markup.goldmark.extensions.footnote]
 backlinkHTML = '<sup><a href="#fnref:%s" class="footnote-backref" role="doc-backlink">↩︎</a></sup>'

Why This Matters:

Without autoPrefix, footnote IDs can conflict across pages in single-page applications or when combining content.

7. Visual Build Progress #

Modern terminals display visual progress bars during builds.

Supported Terminals:

  • Ghostty
  • Windows Terminal
  • Other terminals supporting OSC 9;4 protocol

What You See:

Building sites … ████████████░░░░░░░░ 60%

No Configuration Required: Works automatically if your terminal supports it.

8. Modern Go Runtime #

Hugo runs on the latest Go runtime, bringing:

  • Performance improvements
  • Better error messages
  • Enhanced security (10 security patches)
  • Improved standard library

Security & Performance #

Built-in Security #

Hugo incorporates the latest security patches and best practices:

Best Practices:

[security]
 enableInlineShortcodes = false
 
 [security.exec]
 allow = ['^dart-sass-embedded$', '^go$', '^npx$', '^postcss$']
 osEnv = ['(?i)^(PATH|PATHEXT|APPDATA|TMP|TEMP|TERM)$']
 
 [security.funcs]
 getenv = ['^HUGO_', '^CI$']
 
 [security.http]
 methods = ['(?i)GET|POST']
 urls = ['.*']

Continuous Improvements #

Hugo continuously improves with enhanced:

  • CJK Support: Proper handling of Chinese/Japanese/Korean text in truncation
  • Shortcodes: Reliable nesting and error messages
  • Live Reload: Instant rebuilds when adding content bundles
  • Cross-Platform: Improved Windows and Unix compatibility
  • Type Handling: Robust YAML and data type processing

Migration Guide #

YAML Boolean Migration #

Step 1: Find Issues

grep -r ": yes$\|: no$\|: on$\|: off$" config/
grep -r ": yes$\|: no$\|: on$\|: off$" content/

Step 2: Replace

Old New
: yes : true
: no : false
: on : true
: off : false

Step 3: Test

hugo - debug

Module Version Migration #

Step 1: Check Current Versions

hugo mod graph

Step 2: Add to Config

[[module.imports]]
 path = "github.com/user/module"
 version = "v1.2.3" # Use version from hugo mod graph

Step 3: Clean and Update

hugo mod clean
hugo mod get -u

Practical Examples #

Example 1: Complete Modern Configuration #

# hugo.yaml
baseURL: https://example.com
languageCode: en-us
title: My Modern Site
 
# Use YAML anchors for DRY config
_defaults: &defaults
 author: John Doe
 theme: modern
 
_prod: &prod
 <<: *defaults
 minify: true
 environment: production
 
_dev: &dev
 <<: *defaults
 buildDrafts: true
 environment: development
 
# Current environment (switch based on HUGO_ENVIRONMENT)
params:
 <<: *prod
 
module:
 imports:
 - path: github.com/user/theme
 version: v2.1.0
 - path: github.com/user/components
 version: main
 
markup:
 goldmark:
 extensions:
 footnote:
 autoPrefix: true
 returnLinkContents: ""

Example 2: Random Content Showcase #

<! - layouts/partials/random-showcase.html - >
{{ $allContent := where .Site.RegularPages "Type" "in" (slice "posts" "projects") }}
{{ $totalItems := len $allContent }}
{{ $showCount := 4 }}
 
{{ if gt $totalItems $showCount }}
 {{ $randomIndices := collections.D $showCount $totalItems }}
 
 <section class="random-showcase">
 <h2>Discover More</h2>
 <div class="showcase-grid">
 {{ range $randomIndices }}
 {{ $item := index $allContent . }}
 <article class="showcase-item">
 <h3><a href="{{ $item.Permalink }}">{{ $item.Title }}</a></h3>
 <p>{{ $item.Summary }}</p>
 <span class="type">{{ $item.Type }}</span>
 </article>
 {{ end }}
 </div>
 </section>
{{ end }}

Example 3: Content API with Markdown #

<! - layouts/_default/api.json - >
{{- $pages := where .Site.RegularPages "Type" "posts" | first 50 -}}
{{- $items := slice -}}
{{- range $pages -}}
 {{- $item := dict
 "title" .Title
 "url" .Permalink
 "date" (.Date.Format "2006-01-02")
 "summary" .Summary
 "content_html" .Content
 "content_markdown" (transform.HTMLToMarkdown .Content)
 "tags" .Params.tags
 -}}
 {{- $items = $items | append $item -}}
{{- end -}}
{{- dict "items" $items | jsonify -}}

Best Practices #

Alright, you’ve seen the features. Now let’s talk about how to actually use them without shooting yourself in the foot.

1. Use YAML Anchors (Seriously, Use Them) #

I can’t stress this enough: if you’re still copying and pasting config blocks, you’re doing it wrong. YAML anchors will save you so much pain:

# Good
defaults: &defaults
  setting1: value1
  setting2: value2
 
config1:
  <<: *defaults
  specific: override
 
# Bad - repetitive
config1:
  setting1: value1
  setting2: value2
  specific: override

2. Pin Module Versions #

Ensure reproducible builds:

# Good - explicit versions
[[module.imports]]
 path = "github.com/user/theme"
 version = "v2.1.0"
 
# Acceptable for development
[[module.imports]]
 path = "github.com/user/experimental"
 version = "main"

3. Enable Auto-Prefix for Footnotes #

Prevent ID conflicts:

[markup.goldmark.extensions.footnote]
 autoPrefix = true

4. Use collections.D for Random Content #

More efficient than shuffling:

<! - Good - >
{{ $random := collections.D 5 (len .Pages) }}
 
<! - Less efficient - >
{{ $shuffled := shuffle .Pages | first 5 }}

5. Keep Hugo Updated #

# Check version
hugo version
 
# Update (macOS with Homebrew)
brew upgrade hugo
 
# Verify
hugo version

Conclusion #

Modern Hugo provides sophisticated tools for professional static site development: YAML anchors reduce configuration overhead, collections.D enables efficient random sampling, transform.HTMLToMarkdown simplifies content migration, and direct module version control ensures reproducible builds.

Implementing these features improves code maintainability, build performance, and developer workflow. Start with YAML anchors for immediate configuration cleanup, then integrate collections.D where random sampling is needed. Use transform.HTMLToMarkdown for content migrations and API endpoints.

For ongoing updates, monitor Hugo releases and participate in the Hugo community.

Part 4 covers Hugo’s comprehensive template function library with production examples.

Resources #


This is Part 3 of the Hugo Mastery series. ← Back to Part 2 | Continue to Part 4: Template Functions Mastery →