Проблема: рутина при создании постов

Когда я только начинал вести блог на Hugo, каждый новый пост создавался через боль и страдания:

  1. Создать папку вручную content/posts/название-поста/
  2. Скопировать туда index.md из соседней папки
  3. Отредактировать front matter (дату, заголовок, теги)
  4. Придумать slug для красивого URL
  5. …и не забыть, что папку лучше назвать на латинице

На всё это уходило пара минут чисто механической работы. А когда постов 10–20, это начинает реально бесить.

В этой статье я расскажу, как я решил эту проблему с помощью штатных возможностей Hugo: архетипов (archetypes), пермалинков (permalinks) и правильной организации бандлов (bundles).

Что такое бандл и зачем папка для каждого поста

Hugo поддерживает три типа контента:

Три способа хранения контента в Hugo

  1. Простой файл .md (без папки)

Самый простой способ — просто положить файл мой-пост.md в папку content/posts/:

1
2
3
4
5
content/
└── posts/
    ├── первый-пост.md
    ├── второй-пост.md
    └── третий-пост.md

Плюсы:

  • Максимально просто, ничего создавать не нужно
  • Подходит для простых постов без изображений

Минусы:

  • Все изображения нужно класть в общую папку static/images/
  • Нельзя прикрепить к посту специфичные файлы (PDF, архивы и т.д.)
  • Изображения нужно называть уникально, чтобы не пересекались с другими постами
  1. Leaf bundle (папка + index.md)
1
2
3
4
5
6
content/
└── posts/
    └── мой-пост/
        ├── index.md
        ├── hero.jpg
        └── code-example.py

Плюсы:

  • Все ресурсы поста в одном месте
  • Можно ссылаться на изображения относительно: герой
  • Не нужно думать об уникальности имён файлов

Минусы:

  • Нужно создавать папку (но мы это автоматизировали)
  1. Branch bundle (папка + _index.md)
1
2
3
4
5
6
content/
├── posts/
│   ├── _index.md          <- описывает секцию /posts/
│   ├── первый-пост.md
│   └── мой-пост/
│       └── index.md

Плюсы:

  • _index.md позволяет задать заголовок, описание для всей секции
  • Можно настроить отдельный шаблон для списка постов

Структура моего блога:

1
2
3
4
5
6
7
8
9
content/
├── posts/
│ ├── hugo-slugs-archetypes-bundles/
│ │ ├── index.md
│ │ ├── images/
│ │ │ └── diagram.png
│ │ └── code-example.txt
│ └── другой-пост/
│ └── index.md

Плюсы такого подхода:

  • Все посты хранятся одинаково — папка + index.md. Не нужно думать, какой способ выбрать.
  • Все файлы поста в одном месте
  • Можно удобно ссылаться на изображения: ![схема](images/diagram.png)
  • Не нужно придумывать уникальные имена для картинок глобально
  • Если я захочу экспортировать пост в другой блог, достаточно скопировать одну папку со всеми ресурсами.

Почему папку бандла нужно называть на латинице

Здесь кроется важный момент. Hugo позволяет использовать любые символы в именах папок, включая кириллицу. Но есть две причины использовать латиницу:

  1. Чистые URL. Если папка называется мой-пост, то URL будет /%D0%BC%D0%BE%D0%B9-%D0%BF%D0%BE%D1%81%D1%82/. Браузер это поймёт, но выглядит ужасно.
  2. Slug без транслитерации. Имя папки удобно использовать как slug — последний сегмент URL. А латиница в URL — это стандарт и хороший тон.

Правило: папку называем на латинице (например, my-awesome-post), а заголовок внутри пишем по-русски.

Как автоматизировать создание бандла через консоль

Команда для создания бандла с одной папкой:

1
hugo new content posts/название-папки/index.md

Hugo сам создаст папку, сгенерирует index.md с front matter из архетипа.

Важно: эта команда появилась в Hugo 0.112. В старых версиях нужно было сначала создать папку, потом файл.

Настройка архетипа (archetype)

Архетип — это шаблон для новых файлов. Он лежит в archetypes/default.md (или в archetypes/post-bundle.md для конкретного типа).

Мой архетип выглядит так (TOML-формат):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
---
date = '{{ .Date }}'
lastmod = '{{ .Date }}'
draft = true
title = '{{ replace .File.ContentBaseName "-" " " | title }}'
slug = '{{ .File.ContentBaseName }}'
description = ''
author = 'Кразя'

categories = [
  'uncategorized'
]

tags = [
  'draft'
]
---

Разберём ключевые моменты: Поле Значение title Берёт имя папки, заменяет дефисы на пробелы и делает заглавные буквы. my-awesome-post → My Awesome Post slug Просто берёт имя папки как есть: my-awesome-post .File.ContentBaseName Встроенная переменная Hugo — имя текущей папки без расширения и пути

После создания поста я вручную меняю title на русский и заполняю description, categories, tags.

Чтобы URL выглядел как 2025/03/my-awesome-post/, а не как posts/my-awesome-post/, добавляем в hugo.toml:

1
2
[permalinks]
  posts = "/:year/:month/:slug/"

Теперь при сборке сайта Hugo сам построит нужную структуру. При этом внутри content/ всё остаётся по-прежнему — папка в posts/.

Полный цикл создания поста (без лишних телодвижений)

Вот как теперь выглядит создание нового поста в моём блоге:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# 1. Создаём бандл с латинским именем папки
hugo new content posts/hugo-best-practices/index.md

# 2. Открываем файл и правим русский заголовок, описание, теги
vim content/posts/hugo-best-practices/index.md

# 3. Пишем пост в markdown
# 4. Смотрим локально
hugo server -D

# 5. Публикуем
make deploy

Что ещё можно добавить в front matter

В процессе настройки я выяснил, что Hugo поддерживает много полезных полей:

ПолеНазначение
publishDateОтложенная публикация (не рендерится до указанной даты)
expiryDateАвтоматическое снятие с публикации
lastmodДата последнего изменения (для SEO)
aliasesРедиректы со старых URL
weightРучная сортировка в списке (меньше — выше)
imagesИзображение для Open Graph и Twitter Cards
paramsКастомные параметры для темы

Итог

После всех настроек создание нового поста занимает ровно столько времени, сколько нужно на написание контента. Никакой ручной возни с папками и копированием index.md.

Ключевые выводы:

  • Используйте leaf bundles (папка + index.md) для хранения всех ресурсов поста в одном месте

  • Папки называйте на латинице — это даст чистые URL и автоматический slug

  • Настройте архетип с переменной {{ .File.ContentBaseName }} для автоматической генерации title и slug

  • Добавьте [permalinks] в конфиг для красивых URL с датами

Создавайте новый пост одной командой: bash hugo new content posts/имя-папки/index.md

Теперь можно сосредоточиться на том, ради чего всё затевалось — на содержании.

Если у тебя есть свои лайфхаки по Hugo или ты знаешь, как сделать транслитерацию slug прямо из заголовка — пишите мне в Telegram https://t.me/kpa39l. Обсудим.