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:1313After (DRY with anchors):
defaults: &defaults
author: John Doe
language: en
theme: modern
production:
<<: *defaults
baseURL: https://example.com
development:
<<: *defaults
baseURL: http://localhost:1313Real-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: 3Multilingual 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: 3Environment-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 developmentBreaking 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 falseNew 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.3After:
[[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 development5. 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 conflictsCustom 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 - debugModule Version Migration #
Step 1: Check Current Versions
hugo mod graphStep 2: Add to Config
[[module.imports]]
path = "github.com/user/module"
version = "v1.2.3" # Use version from hugo mod graphStep 3: Clean and Update
hugo mod clean
hugo mod get -uPractical 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: override2. 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 = true4. 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 versionConclusion #
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 →