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.
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
5. Advanced Permalink Tokens
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 →