Developing Website on Hugo (Static Site Generator)
Hugo — static site generator in Go. Main advantage over Jekyll and Eleventy — build speed: 1000 pages build in seconds, not minutes. For large sites (documentation, news portals, multilingual marketing sites) this is critical. Deployment — static files in CDN, no app server.
Project Architecture
mysite/
├── archetypes/ # Templates for new content files
├── assets/ # Files for Hugo Pipes (SCSS, JS, images)
├── config/ # Configuration (hugo.toml or config/_default/)
├── content/ # Markdown content
│ ├── blog/
│ ├── services/
│ └── _index.md
├── data/ # YAML/JSON/TOML data (global variables)
├── i18n/ # Translation files
├── layouts/ # Go templates
│ ├── _default/
│ │ ├── baseof.html # Base template
│ │ ├── list.html
│ │ └── single.html
│ ├── partials/
│ └── shortcodes/
├── static/ # Static files (copied as-is)
└── themes/ # Pluggable themes (git submodule)
hugo.toml Configuration
baseURL = "https://example.com/"
languageCode = "en"
defaultContentLanguage = "en"
title = "Site Title"
theme = "mytheme"
[params]
description = "Site description"
author = "Company"
googleAnalytics = "G-XXXXXXXXXX"
[markup.goldmark.renderer]
unsafe = true # Allow HTML in Markdown
[markup.highlight]
style = "monokai"
lineNos = true
[minify]
disableCSS = false
disableHTML = false
disableJS = false
[outputs]
home = ["HTML", "RSS", "JSON"]
page = ["HTML"]
section = ["HTML", "RSS"]
Multilingual
Hugo has native i18n support. Two approaches:
Approach 1: content by folders
# hugo.toml
defaultContentLanguage = "en"
[languages.en]
languageName = "English"
weight = 1
[languages.ru]
languageName = "Русский"
weight = 2
contentDir = "content/ru"
Approach 2: by file suffix
content/
blog/
post.en.md
post.ru.md
Interface strings stored in i18n/en.toml:
readMore = "Read More"
publishedOn = "Published"
tags = "Tags"
In template: {{ i18n "readMore" }}
Hugo Pipes: Asset Processing
Hugo Pipes replaces Webpack/Vite for basic tasks:
{{/* layouts/partials/head.html */}}
{{ $scss := resources.Get "sass/main.scss" }}
{{ $style := $scss | resources.ToCSS | resources.Minify | resources.Fingerprint }}
<link rel="stylesheet" href="{{ $style.Permalink }}" integrity="{{ $style.Data.Integrity }}">
{{ $js := resources.Get "js/main.js" }}
{{ $script := $js | resources.Minify | resources.Fingerprint }}
<script src="{{ $script.Permalink }}" defer></script>
For complex JS building (ES modules, npm packages), Hugo Pipes integrates with ESBuild:
{{ $opts := dict "targetPath" "js/bundle.js" "minify" true "target" "es2020" }}
{{ $js := resources.Get "js/main.js" | js.Build $opts }}
Taxonomies and Content Types
Taxonomies — built-in Hugo mechanism for tags, categories, authors:
[taxonomies]
tag = "tags"
category = "categories"
author = "authors"
series = "series"
Front matter of post:
---
title: "Article Title"
date: 2024-03-15
draft: false
tags: ["hugo", "jamstack"]
categories: ["Development"]
authors: ["ivan-petrov"]
series: ["Hugo Basics"]
featured_image: "images/cover.jpg"
description: "Brief description for SEO"
---
Page for tag /tags/hugo/ generates automatically and renders through layouts/taxonomy/tag.html.
Working with Data
Data files (data/*.yaml) accessible globally in templates:
# data/team.yaml
- name: "Ivan Petrov"
role: "CTO"
photo: "ivan.jpg"
linkedin: "https://linkedin.com/in/ivanpetrov"
{{/* layouts/partials/team.html */}}
{{ range $.Site.Data.team }}
<div class="team-card">
<img src="/images/team/{{ .photo }}" alt="{{ .name }}">
<h3>{{ .name }}</h3>
<p>{{ .role }}</p>
</div>
{{ end }}
Deployment and CI/CD
Netlify — simplest option. File netlify.toml:
[build]
command = "hugo --minify"
publish = "public"
[build.environment]
HUGO_VERSION = "0.121.0"
HUGO_ENV = "production"
[[headers]]
for = "/*"
[headers.values]
X-Frame-Options = "DENY"
X-Content-Type-Options = "nosniff"
Cache-Control = "public, max-age=31536000, immutable"
GitHub Actions → S3 + CloudFront:
- name: Build Hugo
run: hugo --minify --baseURL="https://example.com/"
- name: Deploy to S3
run: aws s3 sync public/ s3://my-bucket --delete
- name: Invalidate CloudFront
run: aws cloudfront create-invalidation --distribution-id ${{ secrets.CF_DIST_ID }} --paths "/*"
Performance
Static Hugo site with proper setup scores 95–100 Lighthouse points out of the box. Critical settings:
-
resources.Fingerprintfor cache busting -
loading="lazy"for images below fold - Hugo Image Processing to generate WebP and needed sizes:
{{ $img := .Resources.GetMatch "cover.*" | images.Resize "800x WebP" }} - Preload for critical fonts
Timeframe
Simple corporate site on ready theme with custom content — 5–7 days. Development from scratch: architecture + templates + multilingual + CI/CD — 2–4 weeks. Large documentation portal or news site with hundreds of pages — 1–2 months.







