Hugo — Architecture#

Architectural style#

Pipeline-based Monolith with pluggable converters.

Hugo is a single-binary static site generator. Its architecture is a sequential build pipeline (process → assemble → render → post-process) orchestrated by a central site object (HugoSites). A service-locator (Deps) wires together all subsystems at startup using manual dependency injection—no DI framework. Extensibility is limited to defined plugin points: markup converters (goldmark, asciidoc, pandoc, org-mode) registered via the converter.Provider interface, and template functions registered by namespace packages under tpl/.

This is not a microkernel or plugin-based architecture in the general sense. Extension happens at compile time (build-tag editions) or at narrow, well-defined runtime interfaces (converter.Provider). Third-party code cannot add runtime plugins; Hugo is extended by forking or by using the Hugo Modules system to compose themes and content.

Component diagram (textual)#

 CLI (main.go)
      │
      ▼
 commands/            ← simplecobra-based CLI; hugoBuilder orchestrates builds & watch
      │
      ├─ loads ──────► config/allconfig/   ← merged config (files + env + flags)
      │
      └─ creates ────► hugolib.HugoSites   ← one Site per language
                            │
                            ├─ embeds ────► deps.Deps   ← service locator / DI container
                            │                  │
                            │                  ├── hugofs.Fs          (virtual FS, wraps afero)
                            │                  ├── resources.Spec     (resource pipeline)
                            │                  ├── tplimpl.TemplateStore (Go templates)
                            │                  ├── cache/dynacache    (in-memory cache)
                            │                  ├── cache/filecache    (disk cache)
                            │                  ├── helpers.PathSpec   (URL / path logic)
                            │                  ├── helpers.ContentSpec (markup → HTML)
                            │                  └── internal/warpc     (WASM RPC dispatcher)
                            │
                            └─ build pipeline:
                                  process()      ← read content files, parse front matter
                                  assemble()     ← build page trees (radix trie), taxonomy
                                  render()       ← template execution per output format
                                  renderDeferred()← deferred/shortcode post-render pass
                                  postProcess()  ← integrity hashes, URL fingerprinting

 markup/              ← converter registry; goldmark is default
 resources/page/      ← Page type: the central domain object
 tpl/                 ← ~30 namespace packages → registered into Go text/template
 publisher/           ← writes final HTML to public/
 livereload/          ← WebSocket server for hugo server
 watcher/             ← fsnotify wrapper, debounced rebuilds

Core components#

commands / hugoBuilder#

  • Package: github.com/gohugoio/hugo/commands
  • Responsibility: CLI entry point (via simplecobra). hugoBuilder drives the full build, file watching, and live-reload loop. Parses flags, loads config, creates HugoSites, calls Build(), watches for changes, and triggers incremental rebuilds.
  • Key types: rootCommand, hugoBuilder, serverCommand
  • Dependencies: hugolib, config/allconfig, livereload, watcher, hugofs

deps.Deps#

  • Package: github.com/gohugoio/hugo/deps
  • Responsibility: Central dependency container. Holds references to every major subsystem (filesystem, config, cache, template store, resource pipeline, WASM dispatchers). Acts as a service locator passed down to all components that need cross-cutting services.
  • Key types: Deps, DepsCfg, BuildState, Listeners[T]
  • Dependencies: Nearly everything. Deps is the hub; it imports hugofs, config, resources, tpl/tplimpl, cache/dynacache, cache/filecache, helpers, internal/warpc, internal/js.

hugolib.HugoSites / Site#

  • Package: github.com/gohugoio/hugo/hugolib
  • Responsibility: Core build orchestrator. HugoSites holds all Site instances (one per language × role × output version). Site manages content for a single language. The build pipeline (process/assemble/render/post-process) lives in hugo_sites_build.go. Content is organized in a radix trie (doctree) for efficient partial rebuilds.
  • Key types: HugoSites, Site, pageState, pageTrees, BuildCfg, WhatChanged
  • Dependencies: deps, resources/page, markup/converter, tpl, publisher, hugofs, identity, output

config/allconfig#

  • Package: github.com/gohugoio/hugo/config/allconfig
  • Responsibility: Loads and merges configuration from all sources (config files, environment variables HUGO_*, CLI flags, Hugo Modules). Produces a typed Configs / AllProvider struct that all subsystems consume via the config.AllProvider interface.
  • Key types: Config, Configs, RootConfig
  • Dependencies: config, media, output, modules, langs, markup/*_config, navigation, related

resources / resources/page#

  • Package: github.com/gohugoio/hugo/resources, github.com/gohugoio/hugo/resources/page
  • Responsibility: The resource pipeline. resources.Spec is the factory for all resource types (pages, images, JS, CSS, fonts). Page is the central domain object representing a content file, with front matter, content, and all template-facing methods. resource_transformers/ applies transforms (Babel, PostCSS, esbuild, minify, integrity hash).
  • Key types: resources.Spec, page.Page (interface), pageState (implementation), resources.Resource
  • Dependencies: hugofs, helpers, markup/converter, cache, output, media, internal/warpc

markup/converter#

  • Package: github.com/gohugoio/hugo/markup/converter
  • Responsibility: Pluggable content conversion registry. Defines the Converter and Provider interfaces. Registered converters: goldmark (Markdown, default), asciidocext (AsciiDoc), pandoc, rst, org-mode. Each converter takes raw content bytes and returns rendered HTML + metadata (ToC, word count, etc.).
  • Key types: Converter, Provider, ProviderProvider, RenderContext, ResultRender
  • Dependencies: markup/highlight, markup/tableofcontents, config

tpl / tplimpl#

  • Package: github.com/gohugoio/hugo/tpl, github.com/gohugoio/hugo/tpl/tplimpl
  • Responsibility: Hugo’s template function library (~30 namespaced sub-packages: strings, collections, images, js, math, crypto, etc.). tplimpl.TemplateStore compiles and caches Go text/template and html/template trees, registers all namespace methods, and dispatches template execution.
  • Key types: tplimpl.TemplateStore, tpl.RenderingContext, tpl.DeferredExecution
  • Dependencies: deps, resources, hugolib (via interface), markup

hugofs#

  • Package: github.com/gohugoio/hugo/hugofs
  • Responsibility: Virtual filesystem abstraction layered on top of spf13/afero. Assembles the overlay filesystem from all Hugo Module mounts, themes, and the project root. Provides Fs (source + publish dirs) and BaseFs (component-specific sub-filesystems for content, layouts, assets, data, i18n, static).
  • Key types: hugofs.Fs, hugofs.BaseFs, hugofs.FileMetaInfo
  • Dependencies: afero, config, modules

cache (dynacache / filecache)#

  • Package: github.com/gohugoio/hugo/cache/dynacache, github.com/gohugoio/hugo/cache/filecache
  • Responsibility: Two-tier caching. dynacache is an in-process partitioned cache with LRU eviction and identity-based invalidation for partial rebuilds. filecache is an on-disk cache for processed assets (images, JS bundles) with configurable TTL.
  • Key types: dynacache.Cache, dynacache.Partition[K,V], filecache.Caches
  • Dependencies: identity, config

internal/warpc#

  • Package: github.com/gohugoio/hugo/internal/warpc
  • Responsibility: WASM RPC host. Runs compiled WASM modules (Dart Sass, KaTeX, WebP encoder) in-process without CGO. Implements a custom message-passing protocol over stdin/stdout to the WASM sandbox. Enables the extended edition’s CSS/math rendering on platforms where CGO is unavailable.
  • Key types: warpc.Dispatchers, warpc.Dispatcher
  • Dependencies: wazero (WASM runtime, via build tag)

identity#

  • Package: github.com/gohugoio/hugo/identity
  • Responsibility: Fine-grained dependency tracking for incremental rebuilds. Every content node, template, and resource implements identity.Identity. When a file changes, the WhatChanged set of identities propagates through the dependency graph so only affected pages are re-rendered.
  • Key types: Identity, Manager, SignalRebuilder, Identities
  • Dependencies: Deliberately minimal (no imports from domain packages) to avoid cycles.

Data flow#

Full build (hugo build):

1. CLI args parsed by simplecobra → rootCommand.Run()
2. Config loaded: hugo.toml/yaml + env HUGO_* + flags → allconfig.Configs
3. Hugo Modules resolved → go-style module graph traversed, mounts assembled
4. hugofs.BaseFs built: overlay FS from all module mounts + project root
5. Deps.Init() called → PathSpec, ContentSpec, ResourceSpec, TemplateStore initialized
6. HugoSites created (one Site per language)
7. HugoSites.Build() → 4-phase pipeline:
   a. process()   — walk content FS, read files, parse front matter (TOML/YAML/JSON),
                    create pageState objects, populate radix trie (pageTrees)
   b. assemble()  — link pages to sections, build taxonomy terms, compute page kinds,
                    resolve related content, cascade front matter
   c. render()    — for each Site, for each page, for each output format:
                      markup.Converter.Convert(rawContent) → HTML
                      tplimpl.TemplateStore.Execute(template, page) → final HTML
                      publisher.Publish(path, html) → write to public/
   d. renderDeferred() — re-render pages with deferred shortcodes / JS batching
   e. postProcess()    — inject integrity hashes, rewrite resource URLs, write build stats
8. Aliases written (redirect HTML files)
9. Static files synced to public/

Server watch mode (hugo server):

watcher.Watcher detects fsnotify.Event
  → debounce 50ms
  → identity.SignalRebuild(changedIDs)
  → WhatChanged computed from dependency graph
  → HugoSites.Build(events) — partial rebuild:
      only process/assemble/render affected pages
  → livereload.ForceRefresh() → WebSocket → browser reload

Resource pipeline (image processing, JS bundling):

Template calls: {{ $img := resources.Get "logo.png" | resources.Resize "200x" }}
  → resources.Spec.GetResource("logo.png") → reads from hugofs
  → Transform chain: Resize → (cache lookup) → images.Process() → write to /resources/_gen/
  → Returns Resource with .RelPermalink for HTML output

Initialization / Bootstrap#

  1. main()commands.Execute(os.Args[1:]) (5 lines)
  2. commands.newExec() builds the simplecobra command tree; all subcommands registered
  3. On Run, rootCommand calls hugoBuilder.loadConfig()allconfig.LoadConfig():
    • Reads config files (hugo.toml, config/_default/, etc.)
    • Merges language-specific overrides
    • Processes Hugo Module graph (downloads if needed)
  4. hugoBuilder.newHugoSites()NewHugoSites(cfg DepsCfg, configs *allconfig.Configs):
    • Constructs Deps with all providers
    • Calls Deps.Init() — chains PathSpec → ContentSpec → ResourceSpec
    • Creates one Site per language
  5. HugoSites.Build() called directly (batch) or via hugoBuilder.build() (watch loop)

Dependency injection: Manual wiring. Deps struct is the container; providers are set on DepsCfg before Deps.Init() is called. Deps.Clone() produces per-site copies for multilingual builds, sharing caches and the template store. No framework (wire, dig, fx) is used. The pattern is explicit, type-safe, and traceable but creates a god-object Deps that everything depends on.

Configuration#

  • Format: TOML (primary), YAML, JSON, INI. File: hugo.toml or config/ directory (split by environment: config/_default/, config/production/, etc.)
  • Environment variables: HUGO_* prefixed vars override config keys; HUGO_PARAMS_* sets site params.
  • CLI flags: Persistent flags on root command override any file/env config.
  • Resolution order: CLI flags > env vars > environment-specific config dir > default config dir > defaults.
  • Config library: Custom loader in config/allconfig/. Uses github.com/pelletier/go-toml/v2 for TOML, gopkg.in/yaml.v2 for YAML. No Viper. Structured as nested Go structs with field tags for mapstructure decoding.
  • Typed access: All config consumers receive config.AllProvider (interface), not raw maps. Sub-configs accessed via GetConfigSection("markup"), GetConfigSection("security"), etc.

Key design decisions#

  1. Deps as explicit service locator (manual DI): Every subsystem receives a *Deps or a reference it extracts from Deps. This avoids interface proliferation but creates high coupling to the container. The trade-off is testability: tests can inject a minimal Deps with an in-memory filesystem (afero.MemMapFs) without needing a real site. The pattern is Hugo-specific and well-understood by contributors, but Deps is a god-object by any measure.

  2. Radix trie page tree (hugolib/doctree) for incremental builds: Content is organized in a radix trie keyed by path, enabling O(log n) lookups and efficient subtree walks for partial rebuilds. When a file changes, only the affected subtree is invalidated. This is the core mechanism enabling sub-second rebuilds for large sites.

  3. identity package for fine-grained dependency tracking: Each template, page, and resource carries an Identity. Template executions record which identities they depend on. On file change, the dependency graph is walked to compute the minimal WhatChanged set, so renders are skipped for unaffected pages. This is architecturally the most sophisticated part of the codebase.

  4. Virtual filesystem via afero overlays: All content and asset access goes through hugofs, which is an afero overlay assembled from Hugo Module mounts. This cleanly separates the “what to build” concern (filesystem composition) from the “how to build” concern (pipeline), and makes Hugo Modules possible without invasive changes.

  5. Build-tag edition system: Three compile-time editions (none / extended / withdeploy) isolate optional CGO dependencies (LibSass, libwebp) and cloud-SDK code. The WASM fallback in internal/warpc means extended features work without CGO on unsupported platforms. This is a clean solution to the “optional native dependency” problem.