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 rebuildsCore components#
commands / hugoBuilder#
- Package:
github.com/gohugoio/hugo/commands - Responsibility: CLI entry point (via
simplecobra).hugoBuilderdrives the full build, file watching, and live-reload loop. Parses flags, loads config, createsHugoSites, callsBuild(), 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.
Depsis the hub; it importshugofs,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.
HugoSitesholds allSiteinstances (one per language × role × output version).Sitemanages content for a single language. The build pipeline (process/assemble/render/post-process) lives inhugo_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 typedConfigs/AllProviderstruct that all subsystems consume via theconfig.AllProviderinterface. - 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.Specis the factory for all resource types (pages, images, JS, CSS, fonts).Pageis 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
ConverterandProviderinterfaces. 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.TemplateStorecompiles and caches Gotext/templateandhtml/templatetrees, 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. ProvidesFs(source + publish dirs) andBaseFs(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.
dynacacheis an in-process partitioned cache with LRU eviction and identity-based invalidation for partial rebuilds.filecacheis 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, theWhatChangedset 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 reloadResource 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 outputInitialization / Bootstrap#
main()→commands.Execute(os.Args[1:])(5 lines)commands.newExec()builds the simplecobra command tree; all subcommands registered- On
Run,rootCommandcallshugoBuilder.loadConfig()→allconfig.LoadConfig():- Reads config files (hugo.toml, config/_default/, etc.)
- Merges language-specific overrides
- Processes Hugo Module graph (downloads if needed)
hugoBuilder.newHugoSites()→NewHugoSites(cfg DepsCfg, configs *allconfig.Configs):- Constructs
Depswith all providers - Calls
Deps.Init()— chains PathSpec → ContentSpec → ResourceSpec - Creates one
Siteper language
- Constructs
HugoSites.Build()called directly (batch) or viahugoBuilder.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.tomlorconfig/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/. Usesgithub.com/pelletier/go-toml/v2for TOML,gopkg.in/yaml.v2for 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 viaGetConfigSection("markup"),GetConfigSection("security"), etc.
Key design decisions#
Depsas explicit service locator (manual DI): Every subsystem receives a*Depsor a reference it extracts fromDeps. This avoids interface proliferation but creates high coupling to the container. The trade-off is testability: tests can inject a minimalDepswith an in-memory filesystem (afero.MemMapFs) without needing a real site. The pattern is Hugo-specific and well-understood by contributors, butDepsis a god-object by any measure.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.identitypackage for fine-grained dependency tracking: Each template, page, and resource carries anIdentity. Template executions record which identities they depend on. On file change, the dependency graph is walked to compute the minimalWhatChangedset, so renders are skipped for unaffected pages. This is architecturally the most sophisticated part of the codebase.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.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/warpcmeans extended features work without CGO on unsupported platforms. This is a clean solution to the “optional native dependency” problem.