Я долго пользовался WordPress’ом для своего блога и каждый месяц что-то ломалось — то обновление плагина положило admin-панель, то PHP-FPM начал жрать память, то комментаторы прорвались через капчу. В итоге переехал на Hugo, и за полтора года ни одного инцидента. Расскажу, как поднять минимальный технический блог за вечер, без оверинженеринга.

Почему Hugo

  • Один статический бинарь, никаких зависимостей
  • Генерация быстрая — у меня 200 постов рендерится за 1.5 секунды
  • Syntax highlighting через Chroma из коробки
  • Темы простые, можно написать свою за день
  • Деплой — это rsync сгенерированной папки на nginx, и всё

Альтернативы — Jekyll, Astro, Zola, Eleventy. Jekyll медленный, Astro — это уже JS-фреймворк со своими заморочками, Zola — нишевый, Eleventy — слишком гибкий, теряешься в выборе. Hugo — золотая середина для технических блогов.

Установка

Hugo распространяется как один бинарь:

HUGO_VERSION=0.140.0
wget https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_linux-amd64.tar.gz
tar -xzf hugo_extended_${HUGO_VERSION}_linux-amd64.tar.gz
mv hugo /usr/local/bin/
hugo version

Берите именно hugo_extended — без него не работает SCSS/Sass, который нужен большинству тем.

На Debian/Ubuntu можно через apt install hugo, но там обычно версия с отставанием в полгода. Лучше с github.

Создание сайта

hugo new site myblog
cd myblog
git init

Структура:

myblog/
├── archetypes/
├── content/
│   └── posts/
├── data/
├── layouts/
├── static/
├── themes/
└── hugo.toml

Минимальный hugo.toml:

baseURL = "https://example.com/"
languageCode = "ru-ru"
title = "Мой блог"
theme = "minimal"

[params]
  description = "Записки практикующего инженера"
  author = "Anon"

[markup]
  [markup.highlight]
    style = "monokai"
    lineNos = false
    codeFences = true
    guessSyntax = true
    tabWidth = 4

[permalinks]
  posts = "/:slug/"

style = "monokai" — стиль подсветки кода. Полный список — hugo gen chromastyles --help.

Тема

Можно взять готовую, можно написать свою. Готовых на themes.gohugo.io много, но они часто перегружены — таргетинг, аналитика, comments, share-buttons. Для технического блога нужен минимум.

Я написал свою на 4 шаблона. Структура themes/minimal/:

themes/minimal/
├── layouts/
│   ├── _default/
│   │   ├── baseof.html
│   │   ├── list.html
│   │   └── single.html
│   ├── index.html
│   └── partials/
│       ├── head.html
│       └── footer.html
├── static/
│   └── css/
│       └── main.css
└── theme.toml

Базовый шаблон (baseof.html):

<!DOCTYPE html>
<html lang="{{ .Site.LanguageCode }}">
<head>
{{ partial "head.html" . }}
</head>
<body>
<header>
  <h1><a href="{{ .Site.BaseURL }}">{{ .Site.Title }}</a></h1>
</header>
<main>
{{ block "main" . }}{{ end }}
</main>
{{ partial "footer.html" . }}
</body>
</html>

Single-post (single.html):

{{ define "main" }}
<article>
  <h1>{{ .Title }}</h1>
  <time>{{ .Date.Format "2 January 2006" }}</time>
  {{ .Content }}
  <p>Теги: {{ range .Params.tags }}<a href="/tags/{{ . }}/">{{ . }}</a> {{ end }}</p>
</article>
{{ end }}

Главная (index.html):

{{ define "main" }}
<ul class="post-list">
{{ range first 20 (where .Site.RegularPages "Section" "posts") }}
  <li>
    <time>{{ .Date.Format "02.01.2006" }}</time>
    <a href="{{ .Permalink }}">{{ .Title }}</a>
    <p>{{ .Params.summary }}</p>
  </li>
{{ end }}
</ul>
{{ end }}

Первый пост

Архетип для новых постов (archetypes/posts.md):

+++
title = "{{ replace .Name "-" " " | title }}"
date = "{{ .Date }}"
draft = false
tags = []
categories = []
summary = ""
+++

Создаём пост:

hugo new posts/first-post.md

Hugo использует архетип и создаёт файл с заполненным frontmatter. Пишем содержимое в Markdown, сохраняем.

Локальный preview

hugo server -D

-D — показывать drafts. Идём на http://localhost:1313/, видим блог. Hugo делает live-reload, при сохранении файла страница обновляется сама.

Билд и деплой

Билд:

hugo --minify

Сгенерированный сайт лежит в public/. Это просто статика — HTML, CSS, JS. Деплоим на любой статик-сервер. Простейший вариант — nginx:

server {
    listen 443 ssl http2;
    server_name example.com;
    
    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    
    root /var/www/blog;
    index index.html;
    
    location / {
        try_files $uri $uri/ $uri.html =404;
    }
    
    location ~* \.(css|js|woff2)$ {
        expires 30d;
        add_header Cache-Control "public, immutable";
    }
}

Деплой одной строкой:

hugo --minify && rsync -avz --delete public/ user@server:/var/www/blog/

Или через CI — github actions, gitlab pipelines, что угодно. Hugo не требует ничего особого, билдится в одну команду в любом раннере.

Подсветка кода

В блоках кода с language hint Chroma подсветит автоматом:

server {
    listen 80;
}

Стили генерируются в CSS — Hugo может сгенерировать их в файл:

hugo gen chromastyles --style=monokai > static/css/syntax.css

И подключить в head.html:

<link rel="stylesheet" href="/css/syntax.css">

Если в hugo.toml указан style = "..." — Hugo инлайнит стили в HTML напрямую, без отдельного файла. Это удобнее, но раздувает страницу.

RSS

Hugo генерирует RSS-фид автоматом. Доступен по /index.xml или /posts/index.xml. Просто подключите ссылку в шапке:

<link rel="alternate" type="application/rss+xml" 
      title="{{ .Site.Title }}" 
      href="{{ .Site.BaseURL }}index.xml">

Что я бы НЕ делал

  • Не вешайте Disqus или другие comments-системы. Они тащат трекеры, замедляют сайт и устаревают
  • Не подключайте google analytics — есть plausible или umami, они легче
  • Не делайте темы с тёмной/светлой схемой через JS-переключалку — @media (prefers-color-scheme) в CSS решает то же без скриптов
  • Не пишите сразу 10 разных layout’ов — нужен один post-template и один list-template, остальное появится по мере надобности

Итог

Hugo поднимается за час даже без опыта. Бинарь, тема, пара постов, rsync на nginx — и блог в проде. Никакого PHP, никакой базы, никаких обновлений плагинов. Если что-то ломается — это вы сами сломали. Простота и стабильность.