Configuration Support & Module Organization

Comprehensive guide to organizing HBStack module configurations using Hugo's configuration directory structure for better maintainability and separation of concerns.

Configuration Support & Module Organization

This document details how we successfully achieved modular configuration organization for HBStack without breaking Hugo’s core functionality. We solved the challenge of separating module-specific configurations while maintaining proper site rendering and avoiding unintended multilingual site creation.

🎯 The Challenge

Initially, we attempted to separate HBStack module configurations into individual files to improve maintainability:

  • params.blog.yaml - Blog module configuration
  • params.docs.yaml - Documentation module configuration
  • params.pwa.yaml - PWA module configuration
  • params.seo.yaml - SEO module configuration

Problem: Hugo interpreted these as language-specific configurations, creating separate language sites instead of module configurations, resulting in:

  • Broken section pages (/blog/, /docs/) showing only sidebars
  • Missing navigation menus on section pages
  • Content appearing in “language sites” instead of main site

Build Result

  • When server was run following status were shown:
MetricENBLOGDOCSPWASEO
Pages46115151515
Paginator120000
Non-page files350000
Static files2727272727
Processed images830000
Aliases1651111
Cleaned00000

The above metrics are shown when different languages are used. The hugo mistakenly took our separate configuration files as of being different languages. Though the site works but does not create blog and docs main _index page sections unless we do some tricks for example instead of /blogs/ we ask us to show /blog/featured-post where featured-post.md is a markdown file. A lot of struggle was done but it did not gave us satisfactory result.

Also the above does not show us clear pages that belong to Docs or Blog but same metrics, this is also not correct. In order to get the correct pages. We needed to add proper language settings either in language.en.yaml file or in hugo.yaml file.

For example, the following configuration in language.en.yaml shown below shows the correct result of pages belonging to blog and docs:

 1en:
 2  languageName: English
 3  weight: 1
 4
 5blog:
 6  contentDir: content/blog
 7  languageName:: 'Blog'
 8  weight: 2
 9  outputs:
10    home: ['html', 'offline', 'rss', 'searchindex', 'webappmanifest']
11    section: ['html', 'rss']
12docs:
13  contentDir: content/docs
14  languageName: 'Docs'
15  weight: 3
16  outputs:
17    home: ['html', 'offline', 'rss', 'searchindex', 'webappmanifest']
18    section: ['html', 'rss']

The above configuration gives us the correct pages belonging to blog and docs but it creates separate language sites which is not what we want. We want a single main site with proper module configurations.

🔧 Key Discovery - Content Refresh Solution:

The problem of section content not refreshing automatically when treated as languages can be solved with two Hugo settings:

1defaultContentLanguage: en
2defaultContentLanguageInSubdir: true

With these settings, content refreshes automatically. However, this creates a new challenge - disappearing menus on language sites, as Hugo expects separate menu files for each language.

🎯 Menu Inheritance Solutions:

  1. Create language-specific menu files (menus.blog.yaml, menus.docs.yaml)

  2. Use menu merge configuration:

    1menus:
    2  _merge: 'shallow' # Enables menu inheritance across languages
    

Note: The above setting was not used as hugo and hbstack takes care of it automatically.

  1. Use shared content directory for all languages to inherit menus automatically
MetricENBLOGDOCS
Pages46832243
Paginator pages1208
Non-page files3811
Static files272727
Processed images7440
Aliases168894
Cleaned000

Note: The docs section contents were not shown in docs layout but in blog layout. To force the docs section to use docs layout, we need to set the type: docs in the front matter of _index.md file in content/docs/ directory with cascade: as shown below:

1---
2type: docs
3cascade:
4  type: docs
5---

With cascade, all child pages will inherit the type: docs and use the docs layout.

Creating separate menu files

To create separate menu files for blog and docs, we need to create two files menus.blog.yaml and menus.docs.yaml in the config/_default/ directory. And repeat the same menu structure as in menus.en.yaml but with menu items specific to blog and docs. This is not a good approach as it creates redundancy and maintenance overhead.. Rather we can use entirely different menus for Docs and Blog which serves the purpose and also providing a home menu for the main site.


The Solution: Hugo Configuration Directory Structure

We leveraged Hugo’s Configuration Directory feature with recursive parsing to properly organize module configurations.

Final Configuration Structure

 1config/
 2└── _default/
 3    ├── hugo.yaml           # Core Hugo settings
 4    ├── params.yaml         # Core HBStack parameters
 5    ├── menus.en.yaml       # Navigation menus
 6    ├── languages.yaml      # Language configuration
 7    ├── module.yaml         # Hugo modules
 8    └── params/             # 📁 Module-specific parameters
 9        ├── blog.yaml       # Blog module configuration
10        ├── docs.yaml       # Docs module configuration
11        ├── pwa.yaml        # PWA module configuration
12        └── seo.yaml        # SEO module configuration

Key Configuration Files

1. hugo.yaml (Core Hugo Settings)

 1baseURL: https://agsayyed.github.io/hugo-with-docker/
 2title: Hugo With Docker
 3copyright: 'Copyright © 2024-{year} AG Sayyed. All Rights Reserved.'
 4enableRobotsTXT: true
 5timeout: 120s
 6enableEmoji: true
 7
 8# Title configuration
 9title_sections: true
10title_sections_depth: 0
11title_sections_depth_dir: end
12
13# URL configuration
14permalinks:
15  blog: /blog/:year/:month/:title
16
17# Output formats
18outputs:
19  home:
20    - HTML
21    - Offline
22    - RSS
23    - SearchIndex
24    - WebAppManifest
25
26# Taxonomies
27taxonomies:
28  authors: authors
29  tags: tags
30  categories: categories
31  series: series

2. params.yaml (Core HBStack Configuration)

Contains the main HBStack framework configuration including:

  • Core HB settings
  • Header and footer configuration
  • Content panel settings
  • Search and mermaid configurations

3. params/ Directory (Module-Specific Configurations)

Key Insight: By placing module configs in config/_default/params/, Hugo treats them as parameter extensions rather than language configurations.

  • params/blog.yaml: Blog-specific settings (post display, sidebar, archives)
  • params/docs.yaml: Documentation settings (navigation, TOC, breadcrumbs)
  • params/pwa.yaml: Progressive Web App configuration
  • params/seo.yaml: SEO optimization settings

🔧 Technical Implementation

Hugo’s Configuration Merging

Hugo uses a deep merge strategy for the params configuration key:

1[params]
2  _merge = 'deep'

This means all files in config/_default/params/ are recursively merged into the main params object, creating a unified configuration while maintaining organizational separation.

Root Key Omission

In the params/ subdirectory, we omit the root params: key from each file:

✅ Correct (in params/blog.yaml):

1# HBStack Blog Configuration
2hb:
3  blog:
4    list_cols_lg: 3
5    paginate: 12

❌ Wrong:

1params: # <- Don't include this in subdirectory files
2  hb:
3    blog:
4      list_cols_lg: 3

Configuration Watching

Hugo now properly watches the entire configuration structure:

1Watching for config changes in:
2- /config/_default
3- /config/_default/params  # ✅ Recursive watching
4- /config/development

📊 Results & Benefits

Before (Broken)

  • Section pages showed only sidebars and taxonomies
  • Navigation menus disappeared on /blog/ and /docs/
  • Hugo created unintended language sites
  • Configuration was mixed and hard to maintain

After (Working)

  • ✅ Section pages render complete content and navigation
  • ✅ All menus and components work properly
  • ✅ Single main site with proper module configurations
  • ✅ Organized, maintainable configuration structure
  • ✅ Clear separation of concerns

Build Statistics

MetricENBLOGDOCS
Pages46832243
Paginator pages1208
Non-page files3811
Static files272727
Processed images7440
Aliases168894
Cleaned000

🎨 Best Practices Established

1. Configuration Organization

  • Keep core Hugo settings in hugo.yaml
  • Use params.yaml for main HBStack framework configuration
  • Place module-specific configs in params/ subdirectory
  • Use descriptive filenames matching module names

2. File Naming Convention

  • hugo.yaml - Core Hugo configuration
  • params.yaml - Main parameters
  • params/[module].yaml - Module-specific parameters
  • menus.[lang].yaml - Language-specific menus

3. Maintenance Strategy

  • Each module configuration is self-contained
  • Changes to one module don’t affect others
  • Easy to add/remove modules by adding/removing files
  • Clear dependency mapping between modules and configs

🔍 Troubleshooting Guidelines

If Section Pages Break:

  1. Check Hugo is watching config/_default/params/ directory
  2. Verify no params: root key in subdirectory files
  3. Ensure proper YAML syntax in all config files
  4. Test with --disableFastRender to clear cache

Configuration Validation:

1# View complete merged configuration
2hugo config
3
4# Check specific parameter merging
5hugo config | grep -A10 "hb.blog"
6
7# Validate YAML syntax
8hugo --debug

📈 Performance Impact

  • Build Time: Improved due to better organization
  • Configuration Loading: Efficient recursive parsing
  • Development Experience: Hot reloading works for all config files
  • Maintainability: Significantly improved separation of concerns

🚀 Future Enhancements

This configuration structure supports:

  • Easy addition of new HBStack modules
  • Environment-specific overrides in config/development/ or config/production/
  • Module-specific testing and validation
  • Automated configuration generation for new modules

🎨 Context-Specific Menu Implementation

Building on our successful configuration organization, we implemented context-specific navigation menus to provide tailored user experiences for different site sections.

The Menu Challenge

With our multilingual site structure treating /blog/ and /docs/ as separate language contexts, we faced menu duplication and navigation confusion:

  • Same menus appearing across all sections
  • Generic navigation not relevant to current context
  • Poor user experience with overwhelming menu options

Solution: Language-Specific Menu Files

We created dedicated menu files for each section context:

1# config/_default/menus.en.yaml - Main site navigation
2main:
3  - name: Blog
4    url: /blog/
5    weight: 4
6  - name: Docs
7    url: /docs/
8    weight: 1
 1# config/_default/menus.blog.yaml - Blog-specific navigation
 2main:
 3  - name: Blog Home
 4    url: /blog/
 5    weight: 1
 6  - name: Posts
 7    url: /blog/posts/
 8    weight: 2
 9  - name: Categories
10    url: /categories/
11    weight: 3
 1# config/_default/menus.docs.yaml - Documentation-specific navigation
 2main:
 3  - name: Home
 4    url: /
 5    weight: 1
 6  - name: Getting Started
 7    url: /docs/getting-started/
 8    weight: 2
 9  - name: HBStack Guide
10    url: /docs/hbstack-guide/
11    weight: 3

Key Configuration Settings

Menu Isolation: Prevents menu inheritance between language contexts:

1# config/_default/hugo.yaml
2menus:
3  _merge: 'none' # Complete menu isolation

Language Context Definition: Defines section-specific contexts:

 1languages:
 2  en:
 3    languageName: 'English'
 4    weight: 1
 5  blog:
 6    languageName: 'Blog'
 7    weight: 2
 8  docs:
 9    languageName: 'Docs'
10    weight: 3

Benefits Achieved

  • Context-Aware Navigation: Each section shows only relevant menu items
  • Reduced Cognitive Load: Users see navigation that makes sense for their current context
  • Better UX: Specialized navigation guides users through relevant content
  • No Menu Duplication: Clean, isolated menus for each section
  • Maintainable Structure: Easy to modify navigation per section

Content Structure Requirements

For proper context-specific navigation, we created corresponding content structures:

content/
├── blog/
│   ├── _index.md
│   └── posts/
│       └── _index.md
├── docs/
│   ├── _index.md
│   └── getting-started/
└── posts/
    ├── _index.md
    └── [blog posts...]

This ensures that /blog/posts/ displays content within the blog language context with blog-specific menus, while /posts/ remains in the main site context.

📝 Summary

We successfully achieved modular configuration organization by leveraging Hugo’s Configuration Directory feature with recursive parameter merging. This approach provides:

  1. Clean separation of module configurations
  2. Proper site rendering without multilingual side effects
  3. Maintainable structure for long-term development
  4. Hugo best practices compliance

The solution demonstrates that proper understanding of Hugo’s configuration system can solve complex organizational challenges while maintaining full functionality.


This configuration pattern can be applied to any Hugo site using HBStack or similar modular frameworks.

FAQs

Q: Why not use separate language configurations? A: Hugo interprets params.[name].yaml files as language-specific configurations, creating separate sites that break navigation and content rendering.

Q: How does the recursive merging work? A: Hugo recursively parses the config/_default/params/ directory and deeply merges all YAML files into the main params configuration object.

Q: Can I add more modules easily? A: Yes, simply create a new params/[module-name].yaml file in the params directory, and Hugo will automatically include it in the configuration merge.