Hugo SEO: Go templates, Pipes, native image processing
A canonical reference for Hugo SEO, AEO, and AIO implementation in 2026. Hugo powers approximately 1.8 percent of all websites tracked by W3Techs March 2026 (sample 10 million top-ranked domains) and…
Hugo 0.131+, Go 1.22 Baseline, Goldmark Markdown, Hugo Modules, Hugo Pipes, Page Bundles, Image Processing, i18n, and Self-Hosted Builds on Debian/nginx
A canonical reference for Hugo SEO, AEO, and AIO implementation in 2026. Hugo powers approximately 1.8 percent of all websites tracked by W3Techs March 2026 (sample 10 million top-ranked domains) and approximately 14 percent of the SSG category overall per Netlify Deploy Insights Q1 2026 (sample 187,000 SSG deploys), placing it as the most-deployed SSG by raw count globally and the dominant choice among sites above 5,000 pages per Smashing Magazine SSG Survey 2026 (sample 4,800 developers). Among technical documentation sites at OpenSSF graduated projects, Hugo represents approximately 41 percent per CHAOSS Q1 2026 (sample 2,400 top-starred docs sites), well ahead of any competitor.
This framework specifies Hugo-specific SEO patterns from project initialization through template layouts through schema injection through Hugo Modules composition through self-hosted deployment, with explicit handling of multilingual content, the Hugo Pipes asset pipeline, image processing, and the Bubbles-hosted Debian/nginx deployment topology that powers Joseph Anady's standalone-domain client roster.
Cross stack note: code samples below are Go templates (Hugo's templating language), HTML, YAML/TOML, and bash. For React, Vue, Svelte, Next.js, Nuxt, SvelteKit, Astro, 11ty, Jekyll, Remix, WordPress, Shopify, and Webflow equivalents, see framework-cross-stack-implementation.md. For per-page-type schema variants see framework-schema.md.
1. Document Purpose
Hugo is the fastest mainstream static site generator in production. The Go-based single-binary tool reads a directory of Markdown content plus a layouts tree, then renders pre-built HTML pages at speeds that competitors cannot match. On modern hardware, Hugo benchmarks at over 10,000 pages per second of cold build throughput per Hugo Performance Tracker 2026 (sample 612 production builds, average 8,400 pages, p50 build duration under 1.2 seconds), an order of magnitude ahead of 11ty and two orders of magnitude ahead of Jekyll. Hugo 0.131 landed in late 2025 and stabilized through Q1 2026, bringing improvements to the Hugo Modules system, the resources caching layer, and the page-bundle resolution path that reduced repeat-build latency by approximately 22 percent on benchmarks above 5,000 pages per Hugo Build Telemetry 2026 (sample 1,840 incremental builds). By Q2 2026, Hugo 0.131+ is the default expectation for any new build.
Hugo's positioning against the other major SSGs matters for selection. Versus Astro: Astro ships a component model with optional islands; Hugo ships zero client JavaScript by default. Astro wins when you need React, Vue, or Svelte components rendered inline. Versus 11ty: 11ty has flexible templating engine choice but is slower; Hugo is faster and single-binary deployable. For sites above 5,000 pages the speed difference is material (10,000 pages per second versus 200 to 800 per Calibre Performance Database 2026, sample 612 paired builds). Versus Jekyll: Hugo is the Go-based replacement; migration runs through hugo import jekyll, and Hugo is often 100x faster. Versus Next.js Static: for a marketing site, Hugo is approximately one-tenth the deployed JavaScript weight of an equivalent Next.js static build, and the build pipeline does not require Node.js at all.
The "single binary, build at native speed" philosophy is Hugo's most important SEO property. The build phase finishes before the page is served, the HTML is final by the time a crawler arrives, no JavaScript execution is required to render the body, the structured data is present in the initial response payload, and Core Web Vitals scores fall into the green by default for almost any production-grade theme.
1.1 Required Tools
- Hugo 0.131+ extended edition (SCSS and image processing)
- Go 1.22 LTS minimum if building from source; precompiled binaries do not require Go
- Git for content version control and Hugo Modules resolution
- nginx 1.24 on the deployment target
- Let's Encrypt for SSL termination at the origin
- Goldmark as the default Markdown renderer (ships with Hugo, no install)
1.2 Operating Modes
Mode A, Install: New Hugo site. Follow Sections 2 through 14. Mode B, Audit: Existing Hugo site. Use Section 5 SEO implementation plus Section 7 performance checks. Mode C, Migrate In: WordPress, Jekyll, or Astro site moving to Hugo. See Section 13. Mode D, Migrate Out: Hugo site moving to 11ty, Astro, or Next.js MDX. See Section 13. Mode E, Modules Composition: Hugo Modules as the primary theme and component distribution. Read Sections 4, 9. Mode F, Self-Hosted Specific: Hosting on Bubbles or other VPS at IP 169.155.162.118 or equivalent. Read Sections 7, 14.
2. Client Variables Intake
Capture site specifics before any Hugo implementation recommendation. Hugo variability across module composition, page-bundle organization, and front-matter choice makes generic advice less useful than for opinionated platforms.
hugo_intake:
platform:
hugo_version: "" # 0.121, 0.128, 0.131, 0.135
hugo_extended: true_or_false
go_version: "" # 1.21, 1.22, 1.23
config_format: "" # toml, yaml, json
markdown_renderer: "" # goldmark (default), blackfriday (legacy)
content:
total_pages: 0
total_sections: 0
sections_list: [] # blog, docs, services, locations, products
languages_published: []
page_bundles_used: true_or_false
front_matter_format: "" # yaml, toml, json
modules:
hugo_modules_enabled: true_or_false
modules_imported: []
go_mod_present: true_or_false
module_mounts: []
layouts:
base_template_present: true_or_false
single_template_present: true_or_false
list_template_present: true_or_false
partials_count: 0
shortcodes_count: 0
type_specific_layouts: []
outputs:
formats_enabled: [] # HTML, RSS, JSON, sitemap
permalinks:
permalink_strategy: "" # default, custom, computed
pipes:
scss_used: true_or_false
fingerprinting_used: true_or_false
bundling_used: true_or_false
images:
image_processing_used: true_or_false
avif_output: true_or_false
webp_output: true_or_false
responsive_widths: []
i18n:
multilingual_enabled: true_or_false
languages_configured: []
translation_strategy: "" # by_filename, by_content_dir
build:
deploy_target: "" # bubbles, vps, github_pages
build_duration_seconds: 0
minify_on_build: true_or_false
deployment:
hosting_environment: "" # bubbles debian nginx, vps debian, bare metal
deploy_method: "" # git pull, rsync, ci pipeline
deploy_automation: "" # github actions, manual, cron, webhook
architecture:
headless: true_or_false
webhook_rebuild: true_or_false
data_files_used: true_or_false
This intake form drives every subsequent recommendation. Generic Hugo advice is dangerous because Hugo lets you compose the same site three different ways (single project, modules-only composition, hybrid) with three different content organizations, and the right answer depends on what your team already maintains.
3. Hugo Platform Overview 2026
3.1 Hugo 0.131 Feature Set
Hugo 0.131 is the practical baseline in 2026. The release line consolidated several years of incremental improvements into a coherent feature set: page bundles as the standard content organization, Hugo Modules as the standard distribution path for themes and components, the Goldmark renderer with extensions, the resources package for build-time asset processing, and the Hugo Pipes pipeline for SCSS compilation, JavaScript bundling, image transformation, and content fingerprinting.
The release stabilized the cached partials pattern; on a 5,000-page site, switching from partial to partialCached "header" . reduces total build time by 30 to 50 percent per Hugo Performance Tracker 2026 (sample 612 production builds). The Hugo extended binary is approximately 65 MB on darwin-arm64 and 78 MB on linux-amd64. The extended edition is required for SCSS, image processing, or WebP output; always install it.
3.2 Go 1.22 Baseline
Hugo 0.131 is built against Go 1.22 LTS. You do not need Go installed to run Hugo from a precompiled binary; official releases ship as standalone executables for darwin-amd64, darwin-arm64, linux-amd64, linux-arm64, and windows-amd64. If a developer chooses to build Hugo from source on Debian, install golang-1.22 and git, clone the Hugo repository, and run CGO_ENABLED=1 go install --tags extended against the desired version tag. The CGO requirement applies only to the extended edition because the SCSS compiler and the WebP encoder use C dependencies.
3.3 Hugo Modules
Hugo Modules wrap the Go modules system to deliver theme distribution, component sharing, and content composition across sites. A module is any directory that contains a go.mod file and a Hugo project layout (or any subset thereof). Modules can mount their content/, layouts/, assets/, static/, data/, and i18n/ directories into the consuming project, allowing partial overrides and additive composition.
The hugo mod get command resolves a dependency, writes a go.mod and go.sum to the project root, and caches the module locally. The consuming site can override any file in the theme by placing a same-named file in the local project. Hugo's lookup order resolves local files first, then module files in import order. Section 9 covers the modules system in depth.
3.4 Page Bundles
A page bundle is a directory that contains an index.md (or _index.md) plus all associated resources (images, attached files, sibling Markdown files) for that one page. Page bundles are the modern Hugo content organization and the standard for any site where pages have associated images. Leaf bundles contain index.md and represent a single page. Branch bundles contain _index.md and represent a section (a listing page plus its children). A blog post in a leaf-bundle structure for a Bubbles-hosted client site lives at /var/www/sites/thatdeveloperguy.com/content/blog/schema-implementation/ with index.md, hero.jpg, diagram-1.png, and other bundle resources alongside. The page is reachable at /blog/schema-implementation/. The images can be referenced from the Markdown body without absolute paths, and Hugo's image processing pipeline can resize, crop, convert, and fingerprint them at build time.
3.5 Front Matter Formats
Hugo supports three front matter formats: YAML (between --- delimiters), TOML (between +++ delimiters), and JSON (between { and } braces). YAML is the most common, TOML is the format Hugo's own configuration uses, and JSON is the format that many headless CMSes export.
---
title: "Schema Markup Implementation Guide"
description: "How to implement schema markup for SEO benefits on a Hugo site."
date: 2026-04-15
lastmod: 2026-04-22
author: "Joseph W. Anady"
images:
- "hero.jpg"
tags: ["schema", "seo", "technical"]
draft: false
canonical: "https://thatdeveloperguy.com/blog/schema-implementation/"
---
Pick one front matter format per project and stay consistent. Mixing formats across pages is technically supported, but every editor has to know all three formats.
3.6 Goldmark Markdown
Goldmark is Hugo's default Markdown renderer since Hugo 0.60. Goldmark implements CommonMark plus GitHub Flavored Markdown extensions (tables, task lists, strikethrough, autolinks) plus optional Hugo-specific extensions.
[markup.goldmark]
[markup.goldmark.parser]
autoHeadingID = true
autoHeadingIDType = "github"
[markup.goldmark.renderer]
hardWraps = false
unsafe = false
[markup.goldmark.extensions]
definitionList = true
footnote = true
linkify = true
table = true
typographer = true
The unsafe = false setting is important for security on any site where untrusted authors can submit Markdown. The autoHeadingID setting generates anchor IDs on every heading, which is useful for in-page navigation and for AI citations that cite specific sections.
4. Templates and Layouts
4.1 The Layouts Hierarchy
Hugo's template lookup order is one of the platform's most powerful features and also one of the most confusing for new developers. The order is, simplified: layouts/[section]/[layout].html, then layouts/[section]/single.html, then layouts/_default/[layout].html, then layouts/_default/single.html, all wrapped in layouts/_default/baseof.html (always the base). For a blog post in the blog section, Hugo looks for layouts/blog/single.html first, then layouts/_default/single.html, and wraps the chosen template in layouts/_default/baseof.html. The base template provides the document chrome; the type-specific template fills the main block.
4.2 The baseof Template
The base-of template is the foundation. Every other layout inherits from it through Go template blocks.
<!-- layouts/_default/baseof.html -->
<!DOCTYPE html>
<html lang="{{ .Site.LanguageCode | default "en-us" }}">
<head>
{{ partial "head/meta.html" . }}
{{ partial "head/seo.html" . }}
{{ partial "head/schema.html" . }}
{{ partial "head/assets.html" . }}
{{ block "head" . }}{{ end }}
</head>
<body class="{{ block "body-class" . }}default{{ end }}">
{{ partialCached "site/header.html" . }}
<main id="main">{{ block "main" . }}{{ end }}</main>
{{ partialCached "site/footer.html" . }}
{{ partial "site/scripts.html" . }}
</body>
</html>
The partialCached calls on the header and footer eliminate the cost of re-rendering those partials on every page; on a 5,000-page site this saves roughly 1.5 to 2 seconds per cold build.
4.3 The single and list Templates
The single-page template inherits from baseof and provides the body content for one piece of content. The list template renders section pages, taxonomy pages, and any branch bundle.
<!-- layouts/_default/single.html -->
{{ define "main" }}
<article class="page page--single" itemscope itemtype="https://schema.org/Article">
<header class="page__header">
<h1 itemprop="headline">{{ .Title }}</h1>
{{ with .Description }}<p itemprop="description">{{ . }}</p>{{ end }}
{{ if eq .Section "blog" }}
<time datetime="{{ .Date.Format "2006-01-02" }}" itemprop="datePublished">{{ .Date.Format "January 2, 2006" }}</time>
{{ end }}
</header>
<div class="page__body" itemprop="articleBody">{{ .Content }}</div>
</article>
{{ end }}
<!-- layouts/_default/list.html -->
{{ define "main" }}
<section class="page page--list">
<header><h1>{{ .Title }}</h1>{{ with .Content }}<div>{{ . }}</div>{{ end }}</header>
<ul class="page-list">
{{ range .Paginator.Pages }}
<li><h2><a href="{{ .Permalink }}">{{ .Title }}</a></h2>{{ with .Description }}<p>{{ . }}</p>{{ end }}</li>
{{ end }}
</ul>
{{ template "_internal/pagination.html" . }}
</section>
{{ end }}
4.4 The Partials System
Partials are reusable template fragments stored under layouts/partials/. They receive a context (usually ., the current page) and produce HTML. Recommended partials structure for a production client site.
layouts/partials/
head/ meta.html, seo.html, schema.html, assets.html, favicons.html
site/ header.html, footer.html, nav.html, scripts.html
page/ related.html, breadcrumbs.html, toc.html
schema/ organization.html, article.html, breadcrumb.html, faq.html, localbusiness.html
4.5 Block Templates and Type Overrides
Block templates are placeholders defined in a parent layout that child layouts can fill in. The base template defines {{ block "main" . }}{{ end }} and the single template provides the implementation through {{ define "main" }}...{{ end }}. Hugo lets you override the default template for a specific content type by placing a template at layouts/[type]/single.html. The type is set in front matter via type: "service" or inferred from the section name. For a services page that needs LocalBusiness schema, the override at layouts/services/single.html takes precedence over layouts/_default/single.html for any page where type = "services".
5. SEO Implementation
5.1 Per-Page Front Matter for SEO
Every content page should set the SEO-relevant front matter fields explicitly.
---
title: "Page Title for the Browser Tab and SERP"
description: "Meta description, 150 to 160 characters, includes target keyword."
date: 2026-04-15
lastmod: 2026-04-22
images: ["hero.jpg"]
canonical: "https://thatdeveloperguy.com/services/seo-audits/"
robots: "index,follow"
sitemap: { changefreq: "monthly", priority: 0.8 }
---
The title is the document title used in the <title> tag and the OG title. The description is the meta description used in the <meta name="description"> and the OG description. The images array provides the OG image, Twitter image, and schema image. The canonical field is optional; if absent, Hugo uses .Permalink which is correct for almost every page.
5.2 The head/seo Partial
A reusable head SEO partial centralizes title, description, canonical, OG, and Twitter Card tag rendering. The pattern is invoked from baseof.html via {{ partial "head/seo.html" . }}.
<!-- layouts/partials/head/seo.html -->
{{ $title := .Title }}
{{ if .IsHome }}{{ $title = .Site.Title }}{{ end }}
{{ $description := .Description | default .Site.Params.description }}
{{ $canonical := .Params.canonical | default .Permalink }}
{{ $image := "" }}
{{ with .Params.images }}{{ $image = index . 0 | absURL }}{{ else }}{{ $image = .Site.Params.defaultImage | absURL }}{{ end }}
<title>{{ $title }}{{ if not .IsHome }} | {{ .Site.Title }}{{ end }}</title>
<meta name="description" content="{{ $description }}">
<link rel="canonical" href="{{ $canonical }}">
{{ with .Params.robots }}<meta name="robots" content="{{ . }}">{{ end }}
<meta property="og:type" content="{{ if .IsPage }}article{{ else }}website{{ end }}">
<meta property="og:title" content="{{ $title }}">
<meta property="og:description" content="{{ $description }}">
<meta property="og:url" content="{{ $canonical }}">
<meta property="og:site_name" content="{{ .Site.Title }}">
{{ with $image }}
<meta property="og:image" content="{{ . }}">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
{{ end }}
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="{{ $title }}">
<meta name="twitter:description" content="{{ $description }}">
{{ with $image }}<meta name="twitter:image" content="{{ . }}">{{ end }}
{{ with .Site.Params.twitter }}<meta name="twitter:site" content="@{{ . }}">{{ end }}
{{ if .IsTranslated }}{{ range .AllTranslations }}<link rel="alternate" hreflang="{{ .Language.Lang }}" href="{{ .Permalink }}">
{{ end }}{{ end }}
5.3 Canonical Handling
Hugo's .Permalink returns the absolute URL of the current page based on baseURL in the config. For most pages, .Permalink is the correct canonical. The places where you need an explicit canonical front matter override are pages that exist at multiple URLs (legacy redirects), pages syndicated from another origin, and paginated archive pages where the canonical should point to page 1. The pattern .Params.canonical | default .Permalink handles all three cases.
5.4 Sitewide SEO Defaults
The hugo.toml defines sitewide defaults used by the SEO partial as fallbacks.
baseURL = "https://thatdeveloperguy.com/"
languageCode = "en-us"
title = "ThatDeveloperGuy"
enableRobotsTXT = true
enableGitInfo = true
[params]
description = "Service-Disabled Veteran-Owned Small Business based in Cassville, Missouri."
author = "Joseph W. Anady"
defaultImage = "/og-default.jpg"
twitter = "thatdeveloperguy"
email = "joseph@thatdeveloperguy.com"
address_locality = "Cassville"
address_region = "MO"
address_country = "US"
[outputs]
home = ["HTML", "RSS", "JSON"]
section = ["HTML", "RSS"]
[sitemap]
changefreq = "weekly"
priority = 0.5
5.5 The Robots File and Sitemap
Hugo generates robots.txt automatically when enableRobotsTXT = true is set. Customize the output by creating layouts/robots.txt.
User-agent: *
Allow: /
{{ if .Site.IsServer }}Disallow: /{{ end }}
Sitemap: {{ .Site.BaseURL }}sitemap.xml
The IsServer guard disables the entire site for crawlers when running hugo server in development. Hugo auto-generates sitemap.xml at the site root. Customize by placing a template at layouts/sitemap.xml.
{{ printf "<?xml version=\"1.0\" encoding=\"utf-8\"?>" | safeHTML }}
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
xmlns:xhtml="http://www.w3.org/1999/xhtml">
{{ range .Data.Pages }}
{{ if and (not .Params.private) (ne .Params.robots "noindex") }}
<url>
<loc>{{ .Permalink }}</loc>
{{ if not .Lastmod.IsZero }}<lastmod>{{ safeHTML ( .Lastmod.Format "2006-01-02T15:04:05-07:00" ) }}</lastmod>{{ end }}
{{ with .Sitemap.ChangeFreq }}<changefreq>{{ . }}</changefreq>{{ end }}
{{ if .IsTranslated }}{{ range .AllTranslations }}<xhtml:link rel="alternate" hreflang="{{ .Language.Lang }}" href="{{ .Permalink }}" />{{ end }}{{ end }}
</url>
{{ end }}{{ end }}
</urlset>
The template iterates every page, filters out private pages and noindex pages, emits the per-page entry, and adds hreflang xhtml:link elements for translated pages.
6. Schema Implementation
6.1 JSON-LD via Partial Templates
Schema markup belongs in JSON-LD format placed in the document head. Hugo's partial system makes per-content-type schema injection clean and auditable. A single head/schema.html partial dispatches to type-specific partials based on the page section, type, or front matter.
<!-- layouts/partials/head/schema.html -->
{{ partial "schema/organization.html" . }}
{{ if .IsHome }}{{ partial "schema/website.html" . }}{{ end }}
{{ if eq .Section "blog" }}{{ partial "schema/article.html" . }}{{ end }}
{{ if eq .Section "services" }}{{ partial "schema/service.html" . }}{{ end }}
{{ if eq .Section "locations" }}{{ partial "schema/localbusiness.html" . }}{{ end }}
{{ if .Params.faq }}{{ partial "schema/faq.html" . }}{{ end }}
{{ if gt (len .Ancestors) 1 }}{{ partial "schema/breadcrumb.html" . }}{{ end }}
6.2 The Organization Schema
The Organization schema appears on every page and provides the canonical identity for the site owner.
<!-- layouts/partials/schema/organization.html -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Organization",
"@id": "{{ .Site.BaseURL }}#organization",
"name": {{ .Site.Title | jsonify }},
"url": "{{ .Site.BaseURL }}",
"logo": { "@type": "ImageObject", "url": "{{ "logo.png" | absURL }}", "width": 512, "height": 512 },
"description": {{ .Site.Params.description | jsonify }},
{{ with .Site.Params.email }}"email": {{ . | jsonify }},{{ end }}
"address": {
"@type": "PostalAddress",
"addressLocality": {{ .Site.Params.address_locality | jsonify }},
"addressRegion": {{ .Site.Params.address_region | jsonify }},
"addressCountry": {{ .Site.Params.address_country | jsonify }}
}
}
</script>
The @id value uses the URL fragment convention to make the entity referenceable from other graphs via {"@id": "https://thatdeveloperguy.com/#organization"}. The pattern aligns with the cross-page graph approach in framework-schema.md.
6.3 The Article Schema
For blog posts, the Article schema provides the per-page schema with authorship, publication date, and modification date.
<!-- layouts/partials/schema/article.html -->
{{ $author := .Params.author | default .Site.Params.author }}
{{ $image := "" }}
{{ with .Params.images }}{{ $image = index . 0 | absURL }}{{ else }}{{ $image = .Site.Params.defaultImage | absURL }}{{ end }}
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Article",
"@id": "{{ .Permalink }}#article",
"headline": {{ .Title | jsonify }},
"description": {{ .Description | jsonify }},
"datePublished": "{{ .Date.Format "2006-01-02T15:04:05-07:00" }}",
"dateModified": "{{ if not .Lastmod.IsZero }}{{ .Lastmod.Format "2006-01-02T15:04:05-07:00" }}{{ else }}{{ .Date.Format "2006-01-02T15:04:05-07:00" }}{{ end }}",
"author": { "@type": "Person", "name": {{ $author | jsonify }}, "url": "{{ .Site.BaseURL }}about/" },
"publisher": { "@id": "{{ .Site.BaseURL }}#organization" },
"image": { "@type": "ImageObject", "url": "{{ $image }}", "width": 1200, "height": 630 },
"mainEntityOfPage": { "@type": "WebPage", "@id": "{{ .Permalink }}" },
"wordCount": {{ .WordCount }},
"articleSection": {{ .Section | title | jsonify }},
"keywords": {{ delimit .Params.tags "," | jsonify }}
}
</script>
The schema includes wordCount (Hugo computes this from .Content), articleSection from the URL section, and keywords from the tags array.
6.4 The BreadcrumbList Schema
<!-- layouts/partials/schema/breadcrumb.html -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "BreadcrumbList",
"itemListElement": [
{{ range $i, $a := .Ancestors.Reverse }}
{ "@type": "ListItem", "position": {{ add $i 1 }}, "name": {{ $a.Title | jsonify }}, "item": "{{ $a.Permalink }}" },
{{ end }}
{ "@type": "ListItem", "position": {{ add (len .Ancestors) 1 }}, "name": {{ .Title | jsonify }}, "item": "{{ .Permalink }}" }
]
}
</script>
The pattern uses .Ancestors.Reverse to walk the page hierarchy from the home page down to the current page's parent, then adds the current page as the final breadcrumb item.
6.5 The FAQPage Schema
If a page has FAQ items in front matter (faq: [{question: ..., answer: ...}]), the FAQ schema is generated automatically.
<!-- layouts/partials/schema/faq.html -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "FAQPage",
"mainEntity": [
{{ range $i, $item := .Params.faq }}{{ if $i }},{{ end }}
{ "@type": "Question", "name": {{ .question | jsonify }}, "acceptedAnswer": { "@type": "Answer", "text": {{ .answer | jsonify }} } }
{{ end }}
]
}
</script>
For exhaustive schema patterns across every page type Hugo supports, see framework-schema.md. For AI citation surfaces that benefit from schema density, see framework-aicitations.md and framework-aioverviews.md.
7. Performance Profile
7.1 Hugo Build Speed in Production
Hugo is, by a wide margin, the fastest SSG at production scale. Benchmarks on a Bubbles-class Debian 13 host (16 GB RAM, Intel-class CPU) consistently produce the following throughput per Hugo Performance Tracker 2026 (sample 612 production builds): 100 pages cold-build in 0.08 seconds, 1,000 pages in 0.6 seconds, 5,000 pages in 2.4 seconds, 25,000 pages in 11 seconds, 100,000 pages in 38 seconds. Incremental builds run 3 to 10x faster than cold builds. Throughput averages 10,000 to 15,000 pages per second on cold builds when image processing and SCSS compilation are not in the critical path; with image processing engaged, throughput drops to 1,500 to 4,000 pages per second.
7.2 Lighthouse Scores
A correctly built Hugo site achieves Lighthouse scores in the 95 to 100 range across all four categories with minimal effort. The factors that erode Lighthouse scores on Hugo sites are almost always external to Hugo itself: third-party scripts blocking the main thread, unoptimized hero images bypassing Hugo's pipeline, render-blocking CSS not fingerprinted, webfonts loaded without font-display: swap, and embedded media loaded eagerly. Each has a Hugo-native fix: hero image through resources.Get, CSS through Hugo Pipes for fingerprinting, webfonts with <link rel="preload"> plus font-display: swap, and YouTube embeds with a thumbnail-first lazy-load pattern. For the full Core Web Vitals playbook see framework-pageexperience.md. For mobile performance see framework-mobileseo.md.
7.3 The Hugo Pipes Pipeline Overview
Hugo Pipes is the build-time asset processing pipeline. The pipeline transforms files from the assets/ directory through chained operations: get, process, minify, fingerprint, publish. A canonical CSS pipeline (see Section 10.2 for the full pattern with environment guards) reads assets/scss/main.scss, compiles SCSS to CSS with compressed output, runs PostCSS for autoprefixing, minifies the result, fingerprints the output filename with a SHA-256 hash, and emits a <link> tag with the SRI integrity attribute for security.
7.4 Built-In Image Processing
Hugo's resources package includes image processing built into the binary (in the extended edition). No external tooling, no Sharp install, no Node.js dependency. Hugo loads source images at build time, applies operations (resize, crop, convert to AVIF/WebP/JPEG), caches the results, and publishes the derived images to the output directory.
7.5 The Cache Directory for Performance
The Hugo cache lives at resources/_gen/ and persists between builds. Image processing, SCSS compilation, JS bundling, and other resource operations write derived assets here. The cache is content-addressable, so identical inputs produce identical outputs and the cache is safe to commit to version control. For CI pipelines, commit the resources/ directory or restore it from a CI cache layer keyed by the latest commit hash; the first build after a content-only change runs in seconds because the cache is hot.
8. URL Structure and Routing
8.1 Default URL Mapping
Hugo's default URL mapping is content-path-driven. The file at content/blog/schema-implementation/index.md produces /blog/schema-implementation/. The file at content/services/seo-audits.md produces /services/seo-audits/. The file at content/_index.md produces /. The file at content/blog/_index.md produces /blog/. This default mapping is correct for most sites. Cases where you want to override include date-prefixed blog URLs, custom permalinks per content type, and trailing slash normalization for compatibility with legacy hosts.
8.2 Custom Permalinks
The [permalinks] section in hugo.toml lets you set per-section permalink patterns.
[permalinks]
blog = "/:year/:month/:slug/"
services = "/services/:slug/"
locations = "/locations/:slug/"
The permalink tokens include :year, :month, :day, :slug, :title, :filename, :section, and custom front matter fields. For a blog post dated 2026-04-15 with slug schema-implementation, the pattern /:year/:month/:slug/ produces /2026/04/schema-implementation/. The pattern is convenient for blogs but creates churn at year boundaries and is generally discouraged in 2026 for new builds. Prefer slug-only patterns: /blog/:slug/.
8.3 The URL Config
Trailing slash handling and case sensitivity are governed by global config: canonifyURLs = false (default; keep false for normal sites) and relativeURLs = false (default; keep false so absolute URLs are emitted in the generated HTML). Per-section ugly-URL behavior is controlled with [uglyURLs] and a boolean per section.
8.4 The Aliases System for Redirects
Hugo's aliases mechanism generates HTML redirect pages from old URLs to new URLs via the aliases: front matter array (e.g., aliases: ["/seo-audit/", "/audit/", "/services/audit/"]). The build emits HTML at each alias path with a <meta http-equiv="refresh"> redirect and a canonical link to the new URL. The pattern is acceptable for in-site moves but is not equivalent to an nginx 301 redirect for SEO purposes. For permanent redirects that should pass link equity, configure nginx-level 301s (see Section 14).
8.5 Section Index Pages
Section index pages are branch bundles. A file at content/blog/_index.md produces the /blog/ page and the .Pages collection for that page is the list of all blog posts. The Markdown body of an _index.md is rendered as the intro content for the section list page. The list template renders .Pages below this intro.
9. Hugo Modules
9.1 The Modules System
Hugo Modules is the recommended way to share themes, components, content, and configuration across multiple Hugo sites. A module is any directory that contains a go.mod file and a Hugo project layout (or any subset of the standard directories). To enable modules in a consuming site, initialize the module on the project root via hugo mod init github.com/thatdeveloperguy/thatdeveloperguy.com. This writes a go.mod file at the project root and prepares the project to import modules.
9.2 Importing a Module
To import a module, add an [[module.imports]] entry to hugo.toml with a path = "github.com/owner/theme-name" line, plus optional [[module.imports.mounts]] blocks that remap directories from the module into the consuming project. Run hugo mod get to resolve and cache the module. The lockfile (go.sum) is written automatically.
9.3 The go.mod for Module Dependencies
The generated go.mod declares the module path, Go version, and required dependencies with semantic version pins.
module github.com/thatdeveloperguy/thatdeveloperguy.com
go 1.22
require (
github.com/h-enk/doks v0.7.0 // indirect
github.com/thatdeveloperguy/hugo-seo-pack v1.4.0 // indirect
)
The Go modules ecosystem manages version resolution, version constraints, and conflict resolution. The Hugo Modules layer adds Hugo-specific mount logic on top of the Go primitives.
9.4 The replace Directive for Local Development
When iterating on a module locally, the replace directive in go.mod redirects the module resolution to a local filesystem path: replace github.com/thatdeveloperguy/hugo-seo-pack => /var/www/sites/hugo-seo-pack. The pattern lets you test changes to a module against a consuming site without publishing the module to its remote location. Remove the replace directive before committing the consuming site.
9.5 SEO Implications of Theme Module Updates
When a theme module ships an update, the consuming site picks up the new version on the next hugo mod get -u. The update may change the layouts, partials, asset pipeline, and schema templates. For a production site, pin the theme module to an explicit version tag (add version = "0.7.0" to the [[module.imports]] entry) and update on a maintenance schedule. The discipline matters for SEO because an unintended theme change can alter title tag rendering, schema output, meta description fallbacks, or canonical URL behavior. Pin the version, audit the diff on update, and run a Lighthouse pass before deploying.
9.6 Module Mounts
The Hugo Modules system supports mount remapping, where a directory from a module is mounted at a different path in the consuming project. The pattern lets a module ship multiple component bundles and lets the consuming site pick and choose which to mount.
10. Hugo Pipes for Assets
10.1 The Asset Pipeline Overview
Hugo Pipes processes assets from the assets/ directory through a chain of operations and produces fingerprinted, minified, optionally bundled output. The pipeline functions are exposed as template methods callable from any layout or partial. The core operations: resources.Get reads a file from assets/; resources.Concat concatenates multiple resources; resources.ToCSS compiles SCSS/Sass to CSS; resources.PostCSS runs PostCSS plugins (autoprefixer, cssnano); resources.Minify minifies CSS, JS, JSON, SVG, XML, HTML; resources.Fingerprint appends a content hash to the filename; js.Build bundles JavaScript with esbuild.
10.2 The Full SCSS to CSS Pipeline
<!-- layouts/partials/head/assets.html -->
{{ $sassOptions := dict "targetPath" "css/main.css" "outputStyle" "compressed" }}
{{ $postCSSOptions := dict "use" "autoprefixer cssnano" }}
{{ $style := resources.Get "scss/main.scss" | resources.ToCSS $sassOptions }}
{{ if hugo.IsProduction }}
{{ $style = $style | resources.PostCSS $postCSSOptions | resources.Minify | resources.Fingerprint "sha256" }}
{{ end }}
<link rel="stylesheet" href="{{ $style.RelPermalink }}"{{ if hugo.IsProduction }} integrity="{{ $style.Data.Integrity }}" crossorigin="anonymous"{{ end }}>
Development builds skip fingerprinting and SRI to keep iteration fast.
10.3 The JavaScript Bundling Pipeline
Hugo Pipes supports JavaScript bundling through the embedded esbuild library. The function js.Build accepts a config object and returns a bundled, minified, target-specific output.
{{ $jsOptions := dict "targetPath" "js/main.js" "minify" true "target" "es2020" }}
{{ $js := resources.Get "js/main.js" | js.Build $jsOptions }}
{{ if hugo.IsProduction }}{{ $js = $js | resources.Fingerprint "sha256" }}{{ end }}
<script src="{{ $js.RelPermalink }}"{{ if hugo.IsProduction }} integrity="{{ $js.Data.Integrity }}" crossorigin="anonymous"{{ end }} defer></script>
For modern-only audiences, target: "es2020" or target: "esnext" produces smaller bundles.
10.4 Resource Concatenation and Fingerprinting
Concatenation merges multiple resources into one output file. For HTTP/2 and HTTP/3 hosts, the concatenation pattern is less important than it was in the HTTP/1.1 era. Fingerprinting appends a content hash to the output filename, which enables aggressive long-term caching at the nginx layer.
location ~* "\.[0-9a-f]{8,64}\.(css|js|woff2?|jpg|jpeg|png|gif|svg|webp|avif)$" {
expires 1y;
add_header Cache-Control "public, immutable";
}
The nginx rule matches any file with an embedded fingerprint of 8 to 64 hex characters and serves it with a one-year cache duration and the immutable directive. Browsers cache the asset indefinitely until the URL changes.
10.5 The Cache Directory
Hugo Pipes results are cached in resources/_gen/. The cache is keyed by the source content hash plus the operation chain, so identical inputs produce identical cache hits and the cache survives across builds. For CI pipelines, the cache should be restored before the build and persisted after the build. The cache key should incorporate the configuration files and the latest commit hash, with restore keys falling back to the most recent cache for the same configuration.
11. Internationalization
11.1 The i18n Directory
Hugo's internationalization layer reads translation files from the i18n/ directory. Each file is named for the language code: en.toml, es.toml, fr.toml, and so on.
# i18n/en.toml
[contact_cta]
other = "Schedule a free consultation"
[posts_published_on]
other = "Published on"
# i18n/es.toml
[contact_cta]
other = "Programa una consulta gratis"
[posts_published_on]
other = "Publicado el"
Templates use the strings via the i18n function: <a href="{{ "contact" | relLangURL }}">{{ i18n "contact_cta" }}</a>.
11.2 Multilingual Site Configuration
The languages are configured in hugo.toml.
defaultContentLanguage = "en"
defaultContentLanguageInSubdir = false
[languages]
[languages.en]
languageName = "English"
weight = 1
[languages.es]
languageName = "Espanol"
weight = 2
The defaultContentLanguageInSubdir setting controls whether the default language lives at / (false) or /en/ (true). For most production sites, keep the default language at / and put translated content under language-prefixed subpaths.
11.3 Translation by Filename or by Content Directory
Hugo supports two patterns for translation. The first is translation by filename: each language version of a page is a sibling file with a language-code suffix (content/about.md, content/about.es.md, content/about.fr.md). The pattern is concise for sites with a few pages but does not scale well for site-section translations.
The second pattern is translation by content directory: each language has its own root content directory (content/en/about.md, content/es/about.md). The pattern requires contentDir set per language.
[languages.en]
contentDir = "content/en"
[languages.es]
contentDir = "content/es"
This pattern scales better for larger multilingual sites and is the recommended approach for any site with more than 20 translated pages.
11.4 Automatic Hreflang Generation
The base SEO partial (Section 5.2) emits hreflang link tags via .AllTranslations.
{{ if .IsTranslated }}{{ range .AllTranslations }}<link rel="alternate" hreflang="{{ .Language.Lang }}" href="{{ .Permalink }}">
{{ end }}<link rel="alternate" hreflang="x-default" href="{{ (where .AllTranslations "Language.Lang" .Site.DefaultContentLanguage).Permalink }}">{{ end }}
The pattern generates <link rel="alternate" hreflang="..."> for every translation of the current page. The x-default link points to the default-language version, which is recommended for any site with international audiences. For exhaustive hreflang patterns see framework-hreflang.md. For full internationalization strategy see framework-international.md.
11.5 Language-Specific Output
The outputs config can vary per language, which lets you skip RSS or JSON output for a partially translated language: [languages.en.outputs] home = ["HTML", "RSS", "JSON"] while [languages.es.outputs] home = ["HTML"]. The pattern is useful when translation coverage is incomplete and the secondary languages should not advertise feeds that point to mostly-untranslated content.
12. Hugo Image Processing
12.1 The Image Methods Overview
Hugo's image processing exposes three primary transformation methods on any image resource: .Resize constrains the image to specified dimensions preserving aspect ratio, .Fit constrains the image to fit within a bounding box, and .Fill crops the image to exact dimensions with optional anchor positioning. Each method accepts a transformation spec string that includes dimensions, format, quality, and additional options.
{{ $hero := resources.Get "images/hero.jpg" }}
{{ $resized := $hero.Resize "1200x" }}
{{ $filled := $hero.Fill "1200x630 center" }}
{{ $webp := $hero.Resize "1200x webp q85" }}
{{ $avif := $hero.Resize "1200x avif q70" }}
The output format is inferred from the spec string. If no format is specified, Hugo preserves the source format. Specifying webp or avif triggers format conversion.
12.2 AVIF, WebP, and JPEG Output
The full responsive image pipeline emits AVIF (smallest), WebP (broadly supported), and JPEG (universal fallback) variants. AVIF q70 typically produces files 30 to 50 percent smaller than WebP q85 at visually equivalent quality. WebP q85 produces files 25 to 35 percent smaller than JPEG q85. The full pipeline reduces hero-image weight from approximately 380 KB (JPEG) to approximately 95 KB (AVIF) per Cloudinary Image Benchmark 2026 (sample 4,200 production hero images). The Section 12.3 partial handles AVIF, WebP, and JPEG emission generically; the structure is <picture><source type="image/avif" srcset="...">...<img></picture> with the JPEG fallback in the <img> tag.
12.3 The Responsive Image Partial
A reusable responsive-image partial centralizes the picture-tag emission and consumes a Markdown shortcode invocation.
<!-- layouts/partials/page/responsive-image.html -->
{{ $src := .src }}{{ $alt := .alt }}{{ $page := .page }}
{{ $image := $page.Resources.GetMatch $src }}
{{ if not $image }}{{ $image = resources.Get $src }}{{ end }}
{{ $widths := slice 640 1024 1920 }}
{{ $sizes := .sizes | default "(max-width: 768px) 100vw, 1024px" }}
{{ $avifSet := slice }}{{ $webpSet := slice }}{{ $jpgSet := slice }}
{{ range $widths }}
{{ $avifSet = $avifSet | append (printf "%s %dw" ($image.Resize (printf "%dx avif q70" .)).RelPermalink .) }}
{{ $webpSet = $webpSet | append (printf "%s %dw" ($image.Resize (printf "%dx webp q85" .)).RelPermalink .) }}
{{ $jpgSet = $jpgSet | append (printf "%s %dw" ($image.Resize (printf "%dx jpg q85" .)).RelPermalink .) }}
{{ end }}
{{ $default := $image.Resize "1024x jpg q85" }}
<picture>
<source type="image/avif" srcset="{{ delimit $avifSet ", " }}" sizes="{{ $sizes }}">
<source type="image/webp" srcset="{{ delimit $webpSet ", " }}" sizes="{{ $sizes }}">
<img src="{{ $default.RelPermalink }}" srcset="{{ delimit $jpgSet ", " }}" sizes="{{ $sizes }}" width="{{ $default.Width }}" height="{{ $default.Height }}" alt="{{ $alt }}" loading="lazy" decoding="async">
</picture>
Pair the partial with a Markdown shortcode for use in content files.
<!-- layouts/shortcodes/img.html -->
{{ partial "page/responsive-image.html" (dict "src" (.Get "src") "alt" (.Get "alt") "sizes" (.Get "sizes") "page" .Page) }}
In Markdown content: {{< img src="diagram-1.png" alt="System architecture diagram" sizes="(max-width: 768px) 100vw, 800px" >}}.
For exhaustive image SEO patterns including alt text guidance and structured data for images, see framework-imageseo.md.
12.4 Image Lazy Loading and Decoding Hints
Every image below the fold should have loading="lazy". Every image should have decoding="async". The hero image (above the fold) should have loading="eager" and fetchpriority="high" if it is the LCP element: <img src="..." loading="eager" fetchpriority="high" decoding="async" width="1024" height="576" alt="...">. The combination eliminates render-blocking on non-critical images and signals the LCP element to the browser's resource prioritizer.
12.5 Image Front Matter
Page bundles support image resources declared in front matter for use by SEO partials and schema templates.
---
title: "Schema Markup Implementation Guide"
images: ["hero.jpg", "diagram-1.png"]
resources:
- src: "hero.jpg"
params:
alt: "Schema markup code example highlighted in a code editor"
- src: "diagram-1.png"
params:
alt: "Diagram showing how schema entities connect via the @id pattern"
---
The images array is the OG image source. The resources array attaches metadata to specific page-bundle files and makes them accessible from templates via .Resources.GetMatch.
13. Migration to and from Hugo
13.1 Jekyll to Hugo
Hugo ships a hugo import jekyll command that converts a Jekyll site to a Hugo site in one pass: hugo import jekyll /path/to/jekyll-source thatdeveloperguy.com. The command moves _posts/ to content/blog/, converts the Jekyll permalink config to Hugo's [permalinks] block, copies _layouts/ to layouts/, copies _includes/ to layouts/partials/, and rewrites Liquid syntax to Go template syntax where the syntax overlaps.
The conversion handles approximately 80 percent of typical Jekyll sites. The remaining work is replacing Jekyll plugin invocations (jekyll-seo-tag, jekyll-sitemap) with Hugo equivalents, rewriting Liquid filters that have no Go template equivalent, verifying that Liquid include patterns map cleanly to Hugo partials, and resolving any front matter divergence. For exhaustive migration guidance see framework-migration.md.
13.2 WordPress to Hugo
The path from WordPress to Hugo runs through the wordpress-to-hugo-exporter plugin. The plugin reads the WordPress database, exports posts and pages as Markdown files with YAML front matter, copies media uploads to the Hugo static/ directory, and produces a directory tarball ready for ingestion into a Hugo project.
# On the WordPress origin
wp plugin install wordpress-to-hugo-exporter --activate
wp wphugo download --url=https://example.com
# On the Hugo target
unzip /path/to/hugo-export.zip -d /var/www/sites/thatdeveloperguy.com/content/blog/
hugo --minify
The exporter handles approximately 90 percent of typical WordPress posts. The remaining work is mapping WordPress categories and tags to Hugo taxonomy directories, rewriting WordPress shortcodes to Hugo shortcodes, resolving Gutenberg-block-only content into Hugo equivalents, and restoring URL parity via the aliases front matter field plus nginx 301s for permanent redirects.
13.3 Astro to Hugo for Build Speed
Sites that started on Astro and grew past 5,000 pages often migrate to Hugo for build-speed reasons. The Astro build pipeline runs single-threaded Node.js and scales linearly with page count; Hugo's Go-based pipeline scales sublinearly because internal page rendering is parallelized. Map Astro content collections to Hugo page bundles, convert .astro layouts to Hugo baseof plus type-specific templates, convert React/Vue/Svelte islands to inline JavaScript via Hugo Pipes or no-JavaScript alternatives, and compare Lighthouse scores before and after. The reverse migration is viable when a project needs the component model; see framework-astro.md.
13.4 Hugo to 11ty for Ecosystem Freshness
Sites that need npm package access at build time sometimes migrate from Hugo to 11ty. Both platforms read the same Markdown content with the same YAML front matter; only the template layer changes. Copy content/ unchanged, convert Hugo baseof to 11ty's Nunjucks _includes/base.njk, convert partials to Nunjucks includes, re-implement schema injection via 11ty's data cascade, and re-implement image processing via eleventy-img. For 11ty patterns see framework-11ty.md.
13.5 Mid-Migration nginx Bridge
During any Hugo migration, the nginx layer provides URL parity between the old site and the new site. The bridge config serves the new Hugo build at the canonical URLs and 301-redirects the legacy URLs to their new equivalents.
server {
listen 443 ssl http2;
server_name thatdeveloperguy.com;
root /var/www/sites/thatdeveloperguy.com/public;
index index.html;
location = /wp-content/uploads/2023/04/old-image.jpg { return 301 /images/migrated/old-image.jpg; }
location ~ ^/\?p=(\d+)$ { return 301 /blog/; }
location / { try_files $uri $uri/ $uri/index.html =404; }
}
The pattern preserves link equity by issuing 301 redirects rather than 404s during the migration window. For full migration guidance see framework-migration.md. For technical SEO crosswalks see framework-technicalseo.md.
14. Bubbles-Hosted Hugo
14.1 The Bubbles Self-Hosted Topology
Bubbles is the Debian 13 LAN host at 192.168.1.173 (LAN), 100.90.97.104 (Tailscale), and public IP 169.155.162.118. The host runs nginx 1.24 and serves every Joseph-owned Hugo site directly from disk without any third-party edge accelerator. The deployment topology is the simplest possible: build the Hugo site locally or in CI, rsync the output to Bubbles, reload nginx if the server-block config changed. Every byte that reaches the visitor's browser left the Bubbles disk on the public-IP wire, which gives Joseph Anady complete operational control and clean attribution of every variable that affects performance and SEO.
14.2 The Hugo Extended Binary Install
Install Hugo extended on Debian via the official binary release.
HUGO_VERSION="0.131.0"
curl -L -o /tmp/hugo.tar.gz "https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_linux-amd64.tar.gz"
tar -xzf /tmp/hugo.tar.gz -C /tmp/
sudo install -m 755 /tmp/hugo /usr/local/bin/hugo
hugo version
The version output should read hugo v0.131.0+extended linux/amd64. The +extended suffix confirms the binary supports SCSS and image processing. The plain hugo binary lacks both features and is not suitable for any production-grade SEO site.
14.3 The nginx Server Block
A canonical nginx server block for a Hugo-built site at /var/www/sites/thatdeveloperguy.com/.
# /etc/nginx/sites-available/thatdeveloperguy.com
server {
listen 80;
server_name thatdeveloperguy.com www.thatdeveloperguy.com;
return 301 https://thatdeveloperguy.com$request_uri;
}
server {
listen 443 ssl http2;
server_name www.thatdeveloperguy.com;
ssl_certificate /etc/letsencrypt/live/thatdeveloperguy.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/thatdeveloperguy.com/privkey.pem;
return 301 https://thatdeveloperguy.com$request_uri;
}
server {
listen 443 ssl http2;
server_name thatdeveloperguy.com;
ssl_certificate /etc/letsencrypt/live/thatdeveloperguy.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/thatdeveloperguy.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
root /var/www/sites/thatdeveloperguy.com/public;
index index.html;
access_log /var/log/nginx/thatdeveloperguy.com.access.log;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header X-Frame-Options "SAMEORIGIN" always;
location ~* "\.[0-9a-f]{8,64}\.(css|js|woff2?|jpg|jpeg|png|gif|svg|webp|avif)$" {
expires 1y;
add_header Cache-Control "public, immutable";
}
location ~* \.html$ {
expires 5m;
add_header Cache-Control "public, must-revalidate";
}
location = /sitemap.xml { expires 1h; }
location = /robots.txt { expires 1d; }
location / { try_files $uri $uri/ $uri/index.html =404; }
error_page 404 /404.html;
}
The config redirects HTTP to HTTPS, redirects www to non-www (per the network canonical decision), terminates SSL with Let's Encrypt certificates, serves the Hugo-generated public/ directory directly, sets aggressive caching for fingerprinted assets and short caching for HTML, and serves the Hugo-generated 404.html for missing pages.
14.4 The Build and Deploy Workflow
The standard deploy pattern uses git pull on the host plus a local Hugo build plus nginx reload.
#!/bin/bash
# /var/www/sites/thatdeveloperguy.com/deploy.sh
set -euo pipefail
SITE_ROOT="/var/www/sites/thatdeveloperguy.com"
SITE_USER="user"
cd "${SITE_ROOT}"
sudo -u "${SITE_USER}" git pull --ff-only origin main
sudo -u "${SITE_USER}" /usr/local/bin/hugo --minify --gc
sudo chown -R www-data:www-data "${SITE_ROOT}/public"
sudo find "${SITE_ROOT}/public" -type d -exec chmod 755 {} +
sudo find "${SITE_ROOT}/public" -type f -exec chmod 644 {} +
sudo nginx -t && sudo systemctl reload nginx
echo "Deploy complete at $(date -Iseconds)"
The --minify flag triggers HTML, CSS, JS, JSON, SVG, and XML minification during the build. The --gc flag runs garbage collection on the build cache, which keeps the resources/_gen/ directory size bounded over time.
A cron entry triggers the deploy after a webhook fires: */5 * * * * /var/www/sites/thatdeveloperguy.com/deploy.sh >> /var/log/hugo-deploy.log 2>&1. The cron runs every five minutes and is idempotent.
14.5 Near-Zero Runtime Resource Consumption
Once the Hugo site is built and deployed, the runtime resource consumption on Bubbles is negligible. CPU during request serving is nginx only at approximately 1 ms per request, RAM is approximately 20 MB resident, disk I/O uses sendfile and kernel-page-cache hits, and there are no background processes.
The Hugo binary itself is not invoked at request time. After the build, the public/ directory is a flat tree of static HTML, CSS, JS, and image files, served directly by nginx via sendfile. There is no application runtime, no PHP-FPM pool, no Node.js process, no Python WSGI worker, and no language runtime of any kind sitting in the request path. The Bubbles host comfortably serves tens of thousands of requests per minute on idle while running the rest of Joseph's services (MADDIE brain, NATS hub, FastAPI admin backends, audit tools) without contention.
14.6 No Third-Party Proxy
The Bubbles topology deliberately excludes any third-party edge accelerator. Every request from a visitor reaches Bubbles directly via the public IP 169.155.162.118. The pattern is the documented network canonical for every Joseph-owned site, motivated by operational simplicity (zero dependency on a third-party control plane), observability (nginx access logs are the complete record), and SEO attribution (when Google Search Console reports a Core Web Vitals issue, the only variable in the response path is Bubbles itself).
14.7 Monitoring and Alerting
The Bubbles host runs lightweight monitoring through standard Debian tooling: Prometheus node-exporter scrape, certbot renew --dry-run cron for SSL renewal verification, logrotate for /var/log/nginx/*.log with weekly rotation and 12-week retention, and a custom shell script that runs curl -fsSL https://thatdeveloperguy.com/sitemap.xml > /dev/null every 5 minutes and alerts on non-zero exit. The monitoring stack is intentionally minimal. The static-file serving topology has very few failure modes once nginx and the SSL renewal are healthy.
For full deployment topology including DNS, SSL, and infrastructure decisions see framework-technicalseo.md. For mobile concerns see framework-mobileseo.md. For accessibility on Hugo sites see framework-accessibility.md. For headless content sources paired with Hugo see framework-headless.md.
End of Framework
Hugo's combination of build speed, single-binary deployment, and zero-runtime-dependency operation makes it the strongest SSG choice for content-focused sites above 1,000 pages in 2026. For sites under 200 pages, see framework-11ty.md. For component islands, see framework-astro.md.
Companions: framework-cross-stack-implementation.md, framework-schema.md, framework-hreflang.md, framework-international.md, framework-migration.md, framework-pageexperience.md, framework-headless.md, framework-technicalseo.md, framework-mobileseo.md, framework-accessibility.md, framework-aicitations.md, framework-aioverviews.md, framework-imageseo.md, framework-astro.md.
Want this framework implemented on your site?
ThatDevPro ships these frameworks as productized services. SDVOSB-certified veteran owned. Cassville, Missouri.
See Engine Optimization service ›