This blog runs on Hugo. Hugo is a static site generator. No CMS in the background, no database, no PHP. What you end up with is plain static HTML that any web host can serve.

What Hugo Is

Hugo is a static site generator written in Go. From Markdown files, templates and a config, Hugo builds a finished website made of static HTML. Those files go onto any web host you like.

What the browser receives is just HTML, CSS, JavaScript and images. The page is fully generated by the time the request comes in, the server only hands it out.

Hugo is open source and lives at gohugo.io.

Hugo and WordPress

I’ve been working with WordPress for over 20 years, privately and professionally, a lot. Neither is better than the other, just different, and one may suit you more than the other.

WordPress is a dynamic CMS, content lives in a database, rendered on every request, edited in the web back end. Hugo flips that around. From Markdown files Hugo builds HTML once, then the server only serves it. Content is files on disk, editor is your choice.

For my blog I wanted exactly that. Write locally in neovim, in Markdown, without worrying about PHP, databases or plugin compatibility.

Installation

Hugo runs on macOS, Linux and Windows. On the Mac:

brew install hugo

For the other systems, check the Hugo docs. The server itself does not need Hugo installed, you only need it locally for the build.

Anatomy of a Hugo Site

Create a new site with a single command:

hugo new site myblog
cd myblog

By default, Hugo writes a hugo.toml. If you prefer YAML, append --format yaml:

hugo new site myblog --format yaml

I went with YAML because I find it easier to read and PaperMod recommends it in the wiki.

You get this layout:

myblog/
├── archetypes/      Templates for new content
├── assets/          Source files for pipelines, e.g. images or CSS
├── content/         Your posts as Markdown
├── data/            Structured data as YAML or JSON
├── i18n/            Translations for theme strings
├── layouts/         Your own templates, override the theme
├── public/          Generated by the build, this is the finished site
├── static/          Copied 1:1 to public
├── themes/          Themes as a submodule or a clone
└── hugo.yaml        Configuration (or hugo.toml, hugo.yml, hugo.json)

Most of your time is spent in content/ and the theme.

Posts typically live as page bundles under content/blog/<year>/<slug>/. A page bundle is a folder with index.md and everything that belongs to the post. Translations such as index.en.md live in there too.

Configuration

On a fresh site the whole config lives in a single file at the project root. For multiple environments Hugo also supports a split layout under config/_default/ with overrides per environment, e.g. config/production/. That’s how I have it set up here: a base config/_default/config.yml plus a small production variant with the live baseURL. Useful when you want a different baseURL in development than for the production build.

A minimal hugo.yaml example:

baseURL: "https://example.com/"
title: "My Blog"
theme: hugo-PaperMod

defaultContentLanguage: en
languages:
  en:
    languageName: "English"
    weight: 1
  de:
    languageName: "Deutsch"
    weight: 2

params:
  ShowReadingTime: true
  ShowToc: true
  defaultTheme: auto

baseURL is the production address, important for sitemap, RSS and absolute links. theme is the folder name under themes/. With defaultContentLanguage and languages you build multilingual setups. The default language has no language code in its URL path, every other language sits under /<langcode>/. What’s available under params depends on the theme.

Themes, with PaperMod as an Example

A theme provides the look and the layouts. You install it once, configure it through params:, and Hugo uses the theme’s templates whenever you don’t have your own.

I use PaperMod. Actively maintained, ships with a dark-mode toggle, search, archives and a table of contents. The installation guide in the theme’s wiki is thorough, in short, as a Git submodule:

git submodule add --depth=1 https://github.com/adityatelange/hugo-PaperMod.git themes/hugo-PaperMod

Activate it in the config:

theme: hugo-PaperMod

PaperMod has a whole set of its own switches, the list is in the wiki too. It also brings its own shortcodes such as figure, collapse, audio and video.

Writing Content

Posts are Markdown files with a YAML front matter at the top:

---
title: "My First Post"
date: 2026-04-27
description: "Short teaser for previews and SEO."
categories: ["Tech"]
tags: ["hugo"]
draft: false
---

The body goes here. Markdown as usual.

You don’t need anything special for this. Markdown opens in any text editor, from VS Code through Sublime Text to Vim or neovim. Drop the file in the right folder under content/ and the next build picks it up.

For structured templates there are archetypes. You define a template in archetypes/<type>.md, and hugo new fills it in for you.

Hugo Commands You Use Daily

The handful of commands you’ll reach for most often:

# Create a new site
hugo new site myblog

# Create a new post from the default archetype
hugo new content blog/2026/my-post/index.md

# Local server with drafts and live-reload
hugo server --buildDrafts

# Production build (output goes to public/)
hugo --minify

hugo server runs on port 1313. While it’s running, Hugo rebuilds on every file change and the browser reloads on its own.

Shipping the Static Site to a Host

After hugo --minify the whole site sits in public/. What you do with it is up to you. Common options are rsync over SSH, rclone for SFTP, S3 and WebDAV, git push to providers like Netlify or Cloudflare Pages, or aws s3 sync. I like to wrap that in a deploy.sh at the project root that bundles build and upload:

#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "$0")"

hugo --minify

rclone copy public/ <remote>:/<target-path>/ \
  --exclude ".DS_Store" \
  --progress

<remote> is the destination you set up earlier with rclone config, <target-path> is the directory on the host. Make the script executable with chmod +x deploy.sh, then a single call builds and uploads.

Hugo Right Inside the Editor

A lot of that is small, repetitive work that I handle straight from neovim. What that looks like is the topic of a separate series around my plugin hugo-cms.nvim.