Оглавление гайда по Hugo для суровых инженеров:

1. Вводная для тех, кто не в теме

Слушай сюда, инженер. Если ты до сих пор не слыхал про Hugo, значит, либо ты сидел в танке, либо занимался какой-то совсем уж узкоспециализированной херней. Но это поправимо.

Что за зверь этот Hugo и на кой хрен он нужен?

Hugo – это, по сути, генератор статических сайтов. «Статических» – ключевое слово. Он берет твои шаблоны (HTML-каркас), твой контент (обычно в Markdown-файлах) и на выходе выдает тебе готовый набор HTML, CSS и JavaScript файлов. Всё. Никаких тебе баз данных на сервере, никакого PHP, Python или Ruby, которые крутятся и ждут запроса. Просто голые файлы, которые отдаются браузеру пользователя как есть.

На кой хрен он нужен?

  1. Скорость, мать её. Сайты, сгенерированные Hugo, летают. Потому что отдавать статику – это самое быстрое, что может делать веб-сервер. Никаких запросов к БД, никакой серверной логики на каждый чих. Пользователь запросил страницу – сервер ему тут же отдал готовый HTML. Всё. Для блогов, документации, лендингов, сайтов-визиток – это просто пушка.
  2. Безопасность. Раз нет серверной логики, которую можно взломать, нет базы данных, которую можно увести, то и дыр для атак на порядок меньше. Статический сайт – это как бронированная дверь. Можно, конечно, поковырять, но геморроя для хакера гораздо больше, а профита меньше.
  3. Простота хостинга и масштабирования. Статику можно захостить где угодно, хоть на самом дешманском хостинге, хоть на GitHub Pages, Netlify, Vercel, Cloudflare Pages – да хоть в S3-бакете. И масштабируется это дело элементарно: больше пользователей – просто раздаешь статику с CDN, и всё. Никаких тебе сложных настроек балансировщиков нагрузки для динамического контента.
  4. Контроль версий для всего. Контент, шаблоны, конфигурация – всё это текстовые файлы. А значит, всё это прекрасно ложится в Git. Вся история изменений, ветки для экспериментов, совместная работа – всё как ты любишь.
  5. Простота разработки (относительная). Если ты уже знаком с HTML/CSS и не боишься командной строки, то порог входа довольно низкий. Шаблонизатор Go (на котором Hugo написан) тоже не бином Ньютона. Главное – понять философию.

Короче, Hugo – это инструмент, который берет на себя всю рутину по сборке сайта из твоих исходников и выдает на гора оптимизированный, быстрый и безопасный статический сайт. Если тебе не нужна сложная серверная логика на каждый запрос пользователя, то Hugo может сэкономить тебе кучу времени, нервов и денег.

Дальше разберем, почему статика сама по себе – это хорошо, если ты вдруг еще сомневаешься.

Окей, продолжаем ликбез. Почему же статика – это так охрененно, что аж Hugo под нее придумали?

Почему статика – это зашибись (краткий ликбез по преимуществам)

Смотри, инженер, тут всё просто как три копейки, но многие до сих пор предпочитают городить огород с динамикой там, где он нахрен не нужен.

  1. Скорость света (ну, почти). Я уже говорил, но повторюсь: статические страницы отдаются браузеру мгновенно. Серверу не надо каждый раз чесать репу, лезть в базу, собирать страницу из кусков по шаблону, как это делает какой-нибудь WordPress или Joomla на каждый клик. Тут всё уже собрано заранее. Пользователь кликнул – получил. Итог: твои юзеры не ждут, поисковики любят быстрые сайты (привет, SEO!), конверсия растет.

  2. Безопасность уровня «крепость». Нет серверного кода, который обрабатывает пользовательские запросы в реальном времени, – нет и большинства векторов атаки. SQL-инъекции? Забудь. XSS через дыры в серверной логике? Мимо. DDoS на базу данных? Какая база данных, ты о чем? Конечно, сам веб-сервер (Nginx, Apache) можно попытаться положить или найти в нем уязвимость, но это уже совсем другая история, и к твоему сайту она имеет опосредованное отношение. Статика – это как бункер.

  3. Хостинг за копейки (или вообще бесплатно). Раз твой сайт – это просто набор HTML/CSS/JS файлов, его можно положить куда угодно. GitHub Pages, GitLab Pages, Netlify, Vercel, Cloudflare Pages – эти ребята часто предлагают бесплатные тарифы для статических сайтов, да еще и с CDN из коробки. Даже если платно – это всё равно в разы дешевле, чем хостить жирную CMS с базой данных.

  4. Масштабируемость до Луны и обратно. Нужно выдержать наплыв трафика? Для статики это не проблема. Просто подключаешь CDN (Content Delivery Network), и твои файлы разлетаются по серверам по всему миру. Пользователь получает контент с ближайшего к нему сервера. Нагрузка на твой основной хостинг минимальна. Масштабировать динамический сайт с базой данных – это целый геморрой с репликацией, кэшированием, балансировщиками. Со статикой – чихнул и готово.

  5. Надежность как у швейцарских часов. Чем проще система, тем меньше в ней чему ломаться. В статическом сайте ломаться практически нечему. Сервер отдает файлы. Всё. Нет отвалившейся базы, нет упавшего PHP-FPM, нет проблем с версиями интерпретаторов. Сайт просто работает.

  6. Разработка и версионирование – как по маслу. Весь твой сайт – это код и текстовые файлы. Контент в Markdown, шаблоны в HTML/Go Templates, конфиги в TOML/YAML. Всё это прекрасно живет в Git. Ты получаешь всю мощь систем контроля версий: история изменений, ветки, мержи, ревью кода (да, контент – это тоже в каком-то смысле код). Откатиться на предыдущую версию? Легко. Работать командой? Без проблем.

  7. Меньше головной боли с обслуживанием. Нет плагинов, которые надо постоянно обновлять, нет тем, которые ломаются после апдейта ядра CMS, нет патчей безопасности для фреймворка, которые надо срочно накатывать. Собрал сайт – и он работает. Конечно, сам Hugo обновляется, но это процесс сборки, а не работы живого сайта.

Понятно, что статика – не панацея. Если тебе нужна сложная пользовательская логика, личные кабинеты с данными в реальном времени, форумы – тут без динамики никуда. Но для огромного пласта задач – блоги, документация, сайты-портфолио, корпоративные сайты с нечасто меняющимся контентом, лендинги – статика рулит и бибикает.

Так что, если задача позволяет, выбирать статику – это не просто модно, это, разумно и эффективно.

Отлично. Теперь давай разберемся, когда этот ваш Hugo – действительно тема, а когда ты с ним только натрахаешься, а толку будет ноль. Это важно, чтобы не вляпаться по незнанию.

Когда Hugo – твой выбор, а когда лучше даже не начинать

Слушай внимательно, чтобы потом локти не кусал.

Hugo – это то, что доктор прописал, если:

  1. Тебе нужен блог или сайт с документацией. Вот тут Hugo просто король. Markdown для контента, бешеная скорость сборки даже для тысяч страниц, таксономии, секции – всё заточено под это.
  2. Ты пилишь портфолио, личный сайт, сайт-визитку. Где контент меняется нечасто, а главное – скорость загрузки и чтобы выглядело прилично. Меньше геморроя с хостингом, больше времени на сам контент.
  3. Нужен лендинг или промо-сайт. Опять же, скорость загрузки решает. Плюс, всё под контролем в Git, легко откатываться, легко тестировать A/B варианты, меняя ветки.
  4. Корпоративный сайт с информацией о компании, услугах, новостями. Если там нет сложной интерактивности и личных кабинетов с данными “на лету” – Hugo справится на ура.
  5. Скорость, безопасность и минимальные затраты на хостинг – твои главные приоритеты. Если эти три пункта для тебя альфа и омега, то статика, и Hugo как ее представитель, – твой прямой путь.
  6. Ты или твоя команда нормально себя чувствуете с Git и Markdown. Если от вида командной строки никого не тошнит, и написать текст с разметкой – не проблема, то процесс пойдет гладко.
  7. Ты хочешь полный контроль над разметкой и структурой. В отличие от многих CMS, где ты ограничен темой и плагинами, тут ты сам себе хозяин.
  8. Проекты, где контент – это тоже “код” и его важно версионировать, рецензировать, как обычный код. Например, техническая документация, юридические тексты, научные статьи.

А вот когда от Hugo лучше держаться подальше или крепко подумать:

  1. Тебе нужен полноценный интернет-магазин со сложной логикой. Корзины, оплата, управление заказами, личные кабинеты с историей покупок – это всё требует серьезного бэкенда. Hugo может быть витриной (JAMstack-подход с API), но сам по себе он это не вывезет. Слишком много костылей придется городить.
  2. Сайт с большим количеством пользовательского контента, который должен появляться мгновенно. Форумы, социальные сети, доски объявлений, где пользователи постоянно постят что-то новое, и это должно сразу отображаться для всех. Каждый чих пересобирать сайт – не вариант.
  3. Нужна сложная интерактивность, завязанная на серверную логику в реальном времени. Если у тебя на сайте какие-то калькуляторы, которые шлют данные на сервер, тот их обрабатывает и тут же выдает результат, или графики, строящиеся на основе постоянно меняющихся данных из БД – Hugo тут не помощник. Это задачи для полноценных веб-приложений.
  4. Команда, которая будет работать с сайтом, состоит из людей, далеких от IT. Если контент-менеджеры привыкли к визуальным редакторам а-ля WordPress и боятся даже слова “Markdown”, им будет сложно. Конечно, есть CMS-надстройки над статикой (типа Forestry, Netlify CMS, Decap CMS), но это уже дополнительные инструменты.
  5. Проект, где ключевая фича – это персонализация контента на лету на стороне сервера для каждого пользователя. Опять же, статика – это “один для всех” (пока не подключишь JavaScript и API).
  6. Если ты просто не хочешь разбираться с командной строкой, Git, и предпочитаешь “all-in-one” решения с админкой. Hugo – это инструмент для тех, кто готов немного испачкать руки в “коде” и конфигурации.

Короче, главный принцип: если большую часть времени твой сайт просто показывает информацию, а не обрабатывает пользовательские данные или не меняется каждую секунду для каждого пользователя, то Hugo – отличный кандидат. Если же там сплошная динамика и интерактив на сервере – смотри в сторону классических фреймворков и CMS.

На этом с вводной частью, пожалуй, всё. Общее представление, надеюсь, сложилось. Если вопросов нет, можем двигать дальше по плану – к быстрой установке и созданию первого сайта.

Замётано. Хватит теории, пора пачкать руки.

2. Быстрый старт, чтобы сразу в бой

Сейчас покажу, как эту шарманку завести с пол-оборота. Без лишних сантиментов, только хардкор.

Установка (без лишней воды, по-спартански)

С установкой Hugo всё просто, как рельса. Эта хрень написана на Go, поэтому чаще всего это один бинарник, который ты просто качаешь и делаешь доступным системе.

  1. Для нормальных пацанов (Linux/macOS через пакетный менеджер):

    • macOS (Homebrew): brew install hugo
    • Linux (Snap): sudo snap install hugo
    • Linux (APT, для Debian/Ubuntu, но может быть не самая свежая версия): sudo apt install hugo
    • Arch Linux: sudo pacman -S hugo
    • Fedora: sudo dnf install hugo

    Короче, гуглишь “install hugo [твоя_операционка]” – первая ссылка обычно на официальный сайт, там всё расписано.

  2. Для тех, кто любит посложнее (Windows или ручная установка):

    • Windows (Chocolatey): choco install hugo -confirm
    • Windows (Scoop): scoop install hugo
    • Руками: Идешь на официальную страницу релизов Hugo на GitHub. Находишь последний релиз, качаешь архив для своей ОС (обычно hugo_extended_..._你的ОС_Архитектура.tar.gz или .zip). Распаковываешь. Внутри будет бинарник hugo (или hugo.exe для Windows). Этот файл надо положить в директорию, которая есть в твоем системном PATH (чтобы команда hugo была доступна из любой папки в терминале). Или каждый раз указывать полный путь к этому бинарнику, что есть мазохизм.
      • Важно: Качай extended версию. Она нужна для некоторых тем и для обработки SASS/SCSS через Hugo Pipes. Не жмись, разница в размере мизерная, а геморроя потом меньше.
  3. Проверка: Открываешь терминал/командную строку и пишешь:

    hugo version
    

    Если видишь что-то вроде hugo v0.12X.Y-блаблабла extended – ты в игре. Если ошибка – значит, накосячил с установкой или PATH не настроил. Разбирайся, это азы.

Не вижу смысла тут разжевывать каждый чих установки для каждой системы. Ты инженер, а не детсадовец. Официальная дока Hugo по установке (gohugo.io/installation/) тебе в помощь, если что.

hugo new site мой_супер_сайт – создаем проект за три секунды

Установил? Красава. Теперь создадим скелет твоего будущего нетленного творения. Открывай терминал, переходи в папку, где будут лежать твои проекты, и командуй:

hugo new site мой_супер_сайт

Замени мой_супер_сайт на любое адекватное имя для твоего проекта (латиницей, без пробелов, как взрослый). Hugo тут же создаст папку с этим именем и напихает туда базовую структуру каталогов.

Что ты увидишь:

Congratulations! Your new Hugo site is created in /путь/к/твоей/папке/мой_супер_сайт.

Just a few more steps and you're ready to go:

1. Download a theme into the same-named folder.
   Choose a theme from https://themes.gohugo.io/ or
   create your own with the "hugo new theme <THEMENAME>" command.
2. Perhaps you want to add some content. You can add single files
   with "hugo new posts/my-first-post.md".
3. Start the built-in live server via "hugo server".

Visit https://gohugo.io/ to learn more.

Не спеши сразу выполнять все эти пункты, особенно с темой. Сначала разберемся со структурой.

Структура каталогов Hugo: что, где и почему именно так

Заходи в созданную папку мой_супер_сайт. Увидишь там примерно следующее (может чуть отличаться в зависимости от версии Hugo):

  • archetypes/: Здесь лежат “архетипы” – шаблоны для создания новых файлов контента. Например, default.md. Когда ты делаешь hugo new posts/my-post.md, Hugo берет архетип и создает файл с уже готовой Front Matter (метаданными). Удобно, чтобы не писать одно и то же каждый раз.
  • assets/: Сюда кладешь свои “сырые” ассеты, которые будут обрабатываться Hugo Pipes – SASS/SCSS, JavaScript, который нужно бандлить/минифицировать, изображения для обработки. Если просто хочешь положить готовый CSS или картинку – для этого есть static/.
  • content/: Сердце твоего сайта. Здесь живет твой контент в виде Markdown (.md) файлов, организованных по папкам, которые обычно соответствуют разделам сайта. Например, content/posts/ для постов блога, content/about.md для страницы “О нас”.
  • data/: Сюда можно складывать данные в форматах TOML, YAML или JSON (например, список продуктов, контакты). Эти данные потом можно использовать в шаблонах.
  • layouts/: Это твои HTML-шаблоны. Они определяют, как будет выглядеть сайт. Здесь будут лежать базовые шаблоны, шаблоны для списков, отдельных страниц, partials (переиспользуемые куски HTML) и т.д. Если используешь готовую тему, то основные шаблоны будут в папке темы, но ты можешь их переопределять здесь.
  • public/: Сюда Hugo будет складывать готовый статический сайт после сборки командой hugo. Важно: Содержимое этой папки генерируется автоматически. Не редактируй тут ничего руками – всё затрется при следующей сборке. Эту папку ты и будешь деплоить на хостинг.
  • static/: Сюда кидаешь все статические файлы, которые не требуют обработки: готовые CSS, JavaScript, картинки, шрифты, favicon.ico, robots.txt и т.п. Всё, что лежит здесь, будет скопировано в корень папки public/ как есть.
  • themes/: Если используешь готовую тему, она будет лежать здесь в своей подпапке. Hugo будет сначала искать шаблоны и ассеты в layouts/ и assets/ твоего проекта, а если не найдет – то в аналогичных папках активной темы.
  • hugo.toml (или config.toml, config.yaml, config.json): Главный конфигурационный файл твоего сайта. Здесь ты будешь указывать название сайта, базовый URL, выбранную тему, настраивать меню, языки и кучу других параметров. Раньше по умолчанию был config.toml, сейчас Hugo стремится к hugo.toml, но старые форматы тоже поддерживает.

Запомни эти папки, как «Отче наш». Понимание структуры – ключ к успеху с Hugo.

hugo server -D – запускаем локальный сервер и смотрим, что получилось

Итак, скелет есть. Давай его оживим. В терминале, находясь в корневой папке твоего сайта (мой_супер_сайт), выполни:

hugo server -D

Что тут происходит:

  • hugo server: Команда запускает встроенный веб-сервер Hugo. Он шустрый и умеет делать LiveReload – то есть, как только ты сохраняешь изменения в контенте или шаблонах, страница в браузере обновляется автоматически. Мега-удобно для разработки.
  • -D (или --buildDrafts): Флаг, который говорит Hugo собирать и показывать черновики (draft: true в Front Matter). По умолчанию черновики не собираются в финальную версию сайта и не показываются на дев-сервере. На этапе разработки это полезно. Можно еще использовать -F (--buildFuture), чтобы видеть посты с будущей датой, и -E (--buildExpired) для просроченных.

После запуска команды ты увидишь что-то вроде:

                   | EN
-------------------+-----
  Pages            |  4
  Paginator pages  |  0
  Non-page files   |  0
  Static files     |  0
  Processed images |  0
  Aliases          |  1
  Sitemaps         |  1
  Cleaned          |  0

Built in 12 ms
Watching for changes in /путь/к/твоей/папке/мой_супер_сайт/{archetypes,assets,content,data,layouts,static,themes}
Watching for config changes in /путь/к/твоей/папке/мой_супер_сайт/hugo.toml
Environment: "development"
Serving pages from memory
Web Server is available at http://localhost:1313/ (bind address 127.0.0.1)
Press Ctrl+C to stop

Открывай браузер и иди по адресу http://localhost:1313/.

Что ты там увидишь? Скорее всего, пустую страницу или ошибку 404. Не ссы, это нормально! Мы же еще не добавили никакого контента и не выбрали (или не создали) ни одной темы оформления. Hugo собрал “ничего” с “никаким” дизайном.

Чтобы сайт хоть что-то показал, ему нужна тема (шаблоны) и хотя бы одна страница контента. Обычно первым делом подключают какую-нибудь простую тему с themes.gohugo.io или создают пару базовых шаблонов. Но об этом – в следующих сериях нашего марлезонского балета.

Пока что можешь остановить сервер, нажав Ctrl+C в терминале.

Ну как, почувствовал себя создателем сайтов? Это только начало. Дальше будет интереснее – будем наполнять эту структуру жизнью.

3. Контент – всему голова

Переходим к самому мясу – контенту. Без него твой сайт – просто набор красивых (или не очень) HTML-файлов без смысла.

В Hugo контент – это то, ради чего всё затевается. И работать с ним, если понять основные принципы, одно удовольствие.

Markdown – твой основной инструмент. Фичи и как им пользоваться по-человечески.

Если ты до сих пор не писал на Markdown, то самое время начать. Это простой язык разметки, который превращается в HTML. Забудь про уродские WYSIWYG-редакторы из старых CMS, где ты полчаса воюешь со стилями. Тут всё чисто и под контролем.

Почему Markdown?

  • Простота: Синтаксис элементарный, запоминается за 5 минут.
  • Читаемость: Исходный .md файл легко читается даже без конвертации в HTML.
  • Портативность: Текстовые файлы, живут в Git, легко редактируются в любом текстовом редакторе.
  • Hugo его любит: Hugo из коробки отлично работает с Markdown, используя по умолчанию парсер Goldmark (раньше был Blackfriday), который довольно мощный и расширяемый.

Основные фишки Markdown, которые тебе понадобятся (кратко, остальное нагуглишь):

  • Заголовки:

    # H1 Заголовок
    ## H2 Заголовок
    ### H3 Заголовок
    ...и так до H6
    
  • Выделение текста:

    *Этот текст курсивом* или _этот тоже_
    **Этот текст жирным** или __этот тоже__
    ***А этот жирным курсивом*** или ___и этот___
    ~~Этот текст зачеркнут~~
    
  • Списки:

    • Ненумерованные:
      * Пункт 1
      * Пункт 2
        * Вложенный пункт 2.1
      - Или так
      + И даже так
      
    • Нумерованные:
      1. Первый пункт
      2. Второй пункт
      3. Третий пункт
      
  • Ссылки:

    [Текст ссылки](http://example.com "Всплывающая подсказка")
    
  • Изображения:

    ![Альтернативный текст для картинки](/путь/к/картинке.jpg "Заголовок картинки")
    

    Путь может быть относительным или абсолютным. Hugo также умеет обрабатывать изображения, но об этом позже.

  • Цитаты:

    > Это цитата.
    > Вторая строка цитаты.
    
  • Горизонтальная линия:

    ---
    ***
    ___
    
  • Код:

    • Встроенный (inline) код: Вот это слово будет выделено как код.
    • Блоки кода (с подсветкой синтаксиса, если тема поддерживает):
      ```python
      def hello_world():
          print("Hello, Hugo!")
      ```
      
      Указывай язык после тройных кавычек для подсветки.
  • Таблицы (базовый синтаксис):

    | Заголовок 1 | Заголовок 2 |
    | ----------- | ----------- |
    | Ячейка 1    | Ячейка 2    |
    | Ячейка 3    | Ячейка 4    |
    

Этого набора тебе хватит для 95% задач. Hugo через Goldmark поддерживает и более продвинутые фичи, вроде сносок, списков задач и т.д., но это уже детали. Главное – пиши чисто, отделяй блоки пустой строкой, и будет тебе счастье.

Front Matter (YAML/TOML/JSON) – метаданные для твоих страниц. Зачем и как.

Front Matter – это то, что превращает простой Markdown-файл в «умную» страницу для Hugo. Это блок метаданных в самом начале твоего .md файла, который содержит информацию о странице: заголовок, дату, автора, теги, категории, черновик ли это, и любую другую хрень, которую ты захочешь туда запихнуть.

Hugo понимает три формата для Front Matter:

  • YAML: Самый популярный, отделяется тройными дефисами (---).
  • TOML: Похож на INI-файлы, отделяется тройными плюсами (+++).
  • JSON: Классический JSON, заключается в фигурные скобки ({}).

Пример YAML (чаще всего будешь видеть его):

---
title: "Мой Первый Супер Пост"
date: 2025-05-31T22:30:00+02:00
draft: false
author: "Суровый Архитектор"
tags: ["hugo", "статика", "гайд"]
categories: ["Технологии"]
description: "Краткое описание поста для SEO и превью."
some_custom_param: "Какое-то мое значение"
---
А вот здесь уже начинается сам контент поста в Markdown...

Пример TOML:

+++
title = "Мой Первый Супер Пост"
date = 2025-05-31T22:30:00+02:00
draft = false
author = "Суровый Архитектор"
tags = ["hugo", "статика", "гайд"]
categories = ["Технологии"]
description = "Краткое описание поста для SEO и превью."
some_custom_param = "Какое-то мое значение"
+++
А вот здесь уже начинается сам контент поста в Markdown...

Пример JSON:

{
    "title": "Мой Первый Супер Пост",
    "date": "2025-05-31T22:30:00+02:00",
    "draft": false,
    "author": "Суровый Архитектор",
    "tags": ["hugo", "статика", "гайд"],
    "categories": ["Технологии"],
    "description": "Краткое описание поста для SEO и превью.",
    "some_custom_param": "Какое-то мое значение"
}

(Обрати внимание, что после JSON Front Matter обычно не ставят закрывающий разделитель, и сразу идет контент)

Стандартные (предопределенные) переменные Front Matter, которые Hugo понимает из коробки:

  • title: Заголовок страницы. Используется везде – в <title> HTML, в заголовках на странице и т.д.
  • date: Дата публикации. Важно для сортировки постов. Формат – ISO 8601.
  • publishDate: Дата, начиная с которой пост будет опубликован.
  • expiryDate: Дата, после которой пост перестанет публиковаться.
  • draft: true или false. Если true, страница не будет собрана по умолчанию (нужен флаг -D при hugo server или hugo).
  • slug: Часть URL для этой страницы. Если не указан, Hugo сгенерирует его из имени файла или заголовка.
  • url: Позволяет полностью переопределить URL страницы.
  • aliases: Массив старых URL, с которых будет сделан редирект на эту страницу. Полезно при миграции сайта.
  • type: Тип контента (например, post, page). Обычно определяется именем папки в content/, но можно переопределить.
  • layout: Имя макета (шаблона) из папки layouts/, который нужно использовать для этой страницы. Позволяет переопределить стандартный выбор макета.
  • markup: Указывает, какой обработчик использовать для контента (md для Markdown, html, rst для reStructuredText, если настроено).
  • outputs: Позволяет определить, в каких форматах выводить страницу (например, ["HTML", "JSON", "AMP"]).
  • weight: Число для ручной сортировки страниц в списках. Меньший вес – выше в списке.
  • description: Краткое описание, часто используется для мета-тега description в SEO.
  • keywords: Массив ключевых слов.
  • tags, categories: Стандартные таксономии. Можно определять и свои.

Помимо этих, ты можешь добавлять любые свои кастомные параметры. Например, featured_image: "/images/my-post-banner.jpg" или show_sidebar: false. Эти параметры будут доступны в шаблонах через .Params.имя_параметра.

Front Matter – это мозг твоей страницы. Отнесись к нему серьезно.

Типы контента (content types): посты, страницы, кастомные разделы.

Hugo – парень умный и любит порядок. Он определяет тип контента (content type) в основном по тому, в какой папке первого уровня внутри директории content/ лежит твой файл.

  • content/posts/my-first-post.md -> тип контента posts
  • content/projects/super-project.md -> тип контента projects
  • content/about.md (лежит прямо в content/) -> тип контента page (это специальный тип для одиночных страниц). Если точнее, то для файлов непосредственно в content/, тип обычно определяется именем секции, совпадающей с именем файла (без расширения), либо считается частью корневой секции. Для about.md, секция будет about, и тип будет page.

Зачем это нужно? Для каждого типа контента Hugo будет искать свой набор шаблонов в папке layouts/. Например:

  • Для контента типа posts:
    • Одиночный пост: layouts/posts/single.html (или layouts/_default/single.html, если специфичного нет)
    • Список постов: layouts/posts/list.html (или layouts/_default/list.html)
  • Для контента типа projects:
    • Одиночный проект: layouts/projects/single.html
    • Список проектов: layouts/projects/list.html

Это позволяет тебе иметь совершенно разное оформление и структуру для блога, портфолио, страниц документации и т.д.

Ты можешь явно указать тип контента в Front Matter через параметр type: "имя_типа", но обычно в этом нет нужды, если ты правильно структурируешь папки.

Файлы _index.md внутри папок секций (например, content/posts/_index.md) играют особую роль: они предоставляют контент и Front Matter для самой страницы секции (списка).

Таксономии: категории, теги и прочая сортировка контента.

Таксономии – это способ классификации твоего контента. По умолчанию в Hugo есть две: tags (теги, метки) и categories (категории). Но ты можешь определить и свои собственные (например, series, authors и т.д.) в файле конфигурации hugo.toml.

Как это работает:

  1. В Front Matter своего контент-файла ты указываешь теги и категории:
    ---
    title: "Пост про котиков и Hugo"
    date: 2025-06-01
    tags: ["котики", "hugo", "милота"]
    categories: ["Животные", "Технологии"]
    ---
    Контент про котиков...
    
  2. Hugo автоматически собирает все эти значения и создает:
    • Страницы для каждого термина таксономии (например, /tags/котики/, /categories/животные/). На этих страницах будет список всего контента, помеченного этим тегом или категорией.
    • Страницы списков всех терминов для каждой таксономии (например, /tags/, /categories/).

Шаблоны для этих страниц таксономий ищутся в layouts/taxonomy/имя_термина.html (например, layouts/taxonomy/tag.html) и layouts/taxonomy/имя_таксономии.terms.html (например, layouts/taxonomy/tag.terms.html). Если их нет, используются дефолтные layouts/_default/list.html и layouts/_default/terms.html.

Таксономии – мощный инструмент для навигации и организации большого количества контента.

Архетипы: шаблоны для создания однотипного контента, чтобы не копипастить.

Мы уже упоминали папку archetypes/. Архетипы – это шаблоны для твоих контент-файлов. Когда ты создаешь новый контент командой hugo new путь/к/новому/файлу.md, Hugo ищет архетип, который соответствует этому пути, и использует его для создания файла.

  • Если ты делаешь hugo new posts/my-new-post.md, Hugo сначала поищет archetypes/posts.md.
  • Если не найдет, поищет archetypes/default.md.
  • Если и его нет, создаст файл с минимальным Front Matter (обычно title, date, draft: true).

Пример файла archetypes/posts.md:

---
title: "{{ replace .Name "-" " " | title }}"  # Берет имя файла, заменяет дефисы на пробелы, делает заглавные буквы
date: {{ .Date }}                             # Текущая дата и время
draft: true
tags: [""]
categories: [""]
author: "Мое Имя По Умолчанию"
# Еще какие-то параметры, специфичные для постов
---

Тут можно даже добавить какой-то текст по умолчанию для тела поста.
Например, напоминание: "Не забудь добавить картинку!"

Здесь {{ .Name }} и {{ .Date }} – это переменные, которые Hugo подставит при создании файла. replace .Name "-" " " | title – это функция шаблонизатора Go, которая форматирует имя файла в заголовок.

Использование архетипов здорово экономит время и помогает поддерживать консистентность Front Matter для разных типов контента. Создал архетип один раз – и потом штампуешь новые страницы по образу и подобию.

Фух. Про контент вроде всё основное рассказал. Это база, на которой всё строится. Дальше будем смотреть, как этот контент красиво вывести с помощью шаблонов.

4. Шаблоны – лицо сайта

Итак, контент у нас есть (ну, или мы знаем, как его делать). Теперь надо, чтобы это всё не выглядело как говно мамонта, а имело приличный вид. За это в Hugo отвечают шаблоны. Они лежат в папке layouts/ (или в папке темы themes/имя_темы/layouts/).

Go Templates – основы основ (синтаксис, переменные, функции)

Hugo использует встроенный в язык Go шаблонизатор. Два основных пакета – text/template и html/template. Последний – умнее, он автоматически экранирует данные, чтобы ты случайно не создал XSS-уязвимость. Hugo в основном использует html/template для HTML-страниц.

Синтаксис этого дела на первый взгляд может показаться немного спартанским, особенно если ты привык к чему-то типа Jinja2 или Twig. Но он мощный, и главное – быстрый.

Основные конструкции, которые надо знать:

  1. Вывод данных (Actions): Всё, что ты хочешь вывести в HTML, заключается в двойные фигурные скобки {{ }}.

    <h1>{{ .Title }}</h1>
    <p>Эта страница была опубликована {{ .Date.Format "02 January 2006" }}.</p>
    
  2. “Точка” (. или “the dot”): Это самое главное понятие. Точка . – это текущий контекст, текущий объект, с которым ты работаешь. Внутри шаблона для одиночной страницы (single.html) точка . будет ссылаться на объект этой страницы. Внутри цикла по постам . будет ссылаться на текущий пост в итерации. Контекст может меняться.

    • {{ .Title }}: Вывести заголовок текущей страницы.
    • {{ .Content }}: Вывести основное содержимое текущей страницы (то, что ты написал в Markdown после Front Matter).
    • {{ .Permalink }}: Вывести постоянную ссылку на текущую страницу.
    • {{ .Params.имя_твоего_параметра }}: Доступ к кастомным параметрам из Front Matter. Например, {{ .Params.featured_image }}.
  3. Переменные: Можно создавать свои переменные внутри шаблона.

    {{ $author := .Params.author | default "Неизвестный Автор" }}
    <p>Автор: {{ $author }}</p>
    

    Здесь мы создаем переменную $author. Если в Front Matter есть author, берем его, если нет – используем “Неизвестный Автор”. Переменные начинаются со знака доллара $.

  4. Функции и пайплайны (|): В Go Templates есть куча встроенных функций, и ты можешь передавать результат одной функции на вход другой через пайплайн |.

    {{ "привет, мир" | upper }}  {{ .WordCount }} слов.       {{ $originalUrl := "https://example.com/?q=hugo rocks" }}
    {{ $escapedUrl := $originalUrl | htmlEscape }} <a href="{{ $escapedUrl }}">Ссылка</a>
    

    Функций много: eq (равно), ne (не равно), gt (больше), lt (меньше), len (длина), printf, safeHTML, safeJS, humanize и т.д. Список ищи в доках Hugo.

  5. Управляющие конструкции:

    • if/else:

      {{ if .Params.show_author }}
          <p>Автор: {{ .Params.author }}</p>
      {{ else if .Params.show_default_author }}
          <p>Автор: Команда сайта</p>
      {{ else }}
          <p>Автор не указан</p>
      {{ end }}
      

      Условия могут быть сложными: {{ if and (gt .WordCount 100) .Params.is_long_read }}.

    • range (циклы): Для перебора списков (например, страниц, тегов).

      <ul>
      {{ range .Site.RegularPages.ByDate.Reverse.Limit 5 }} <li><a href="{{ .RelPermalink }}">{{ .Title }}</a> ({{ .Date.Format "2006-01-02" }})</li>
      {{ else }}
          <li>Пока нет постов для отображения.</li> {{ end }}
      </ul>
      

      Внутри range точка . меняет свой контекст на текущий элемент итерации.

    • with: Позволяет изменить контекст точки . на определенное значение, если оно существует. Удобно, чтобы не писать .Params.section.subsection.field много раз.

      {{ with .Params.author_details }}
          <p>Об авторе: {{ .name }}, {{ .bio }}</p> {{ else }}
          <p>Детали автора не указаны.</p>
      {{ end }}
      
  6. Комментарии:

    {{/* Это комментарий в шаблоне Hugo. Он не попадет в итоговый HTML. */}}
    
  7. Пробелы и дефисы: Иногда ты увидишь {{- .Value -}} или {{- if ... }}. Дефис - рядом с фигурными скобками убирает пробельные символы (пробелы, табы, переводы строк) до или после тега шаблонизатора. Помогает делать HTML-вывод чище, без лишних пустых строк.

    • {{- убирает пробелы слева.
    • -}} убирает пробелы справа.

Это, конечно, верхушка айсберга. Шаблонизатор Go позволяет делать довольно хитрые вещи. Главное – постоянно помнить про контекст (точку .) и знать, какие переменные и функции доступны в данный момент. Официальная документация Hugo по шаблонам (gohugo.io/templates/) – твой лучший друг.

Поначалу может показаться непривычно, но как только набьешь руку, будешь щелкать эти шаблоны как орешки. Главное, не бойся экспериментировать и смотреть, как устроены шаблоны в готовых темах.

Дальше по плану – базовые шаблоны и блоки. Это как раз про то, чтобы не повторять один и тот же HTML-код на каждой странице.

Базовые шаблоны и блоки (DRY, мать его)

Сейчас расскажу про одну из самых полезных фишек в шаблонизации Hugo – как не писать одно и то же по сто раз.

Принцип DRY (Don’t Repeat Yourself) – «Не повторяйся» – это святое для любого вменяемого разработчика. И Hugo помогает тебе этому принципу следовать с помощью базовых шаблонов (base templates) и блоков (blocks).

Представь себе типичный сайт: у всех страниц есть общий <head>, шапка (header), подвал (footer), может быть, боковая панель. Было бы тупо копипастить весь этот HTML-каркас в каждый шаблон (single.html, list.html и т.д.). Вот для этого и нужен базовый шаблон.

Что такое базовый шаблон?

Это главный HTML-скелет твоего сайта. Обычно он называется baseof.html и лежит в layouts/_default/baseof.html (это самый общий, для всех типов контента, если нет более специфичного). Ты можешь также определить baseof.html для конкретных секций, например, layouts/posts/baseof.html – он будет использоваться для всех страниц в секции posts.

В этом baseof.html ты определяешь общую структуру и размечаешь блоки – это как бы «дырки» или «плейсхолдеры», которые будут заполняться содержимым из более конкретных шаблонов (например, из шаблона для отдельного поста или списка постов).

Как это выглядит на практике?

Пример layouts/_default/baseof.html:

<!DOCTYPE html>
<html lang="{{ .Site.LanguageCode | default "en" }}">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{{ block "title" . }}{{ .Site.Title }}{{ end }}</title> {{/* Блок для заголовка страницы */}}
    <link rel="stylesheet" href="/css/style.css">
    {{ block "head_extra" . }}{{ end }} {{/* Блок для дополнительных стилей/скриптов в head */}}
</head>
<body>
    <header>
        <h1><a href="{{ .Site.BaseURL }}">{{ .Site.Title }}</a></h1>
        <nav>
            {{/* Тут может быть меню, например, через partial */}}
            {{ partial "nav.html" . }}
        </nav>
    </header>

    <main>
        {{ block "main" . }}
            {{/* Сюда будет вставляться основное содержимое страницы */}}
            <p>Это содержимое по умолчанию, если блок 'main' не определен.</p>
        {{ end }}
    </main>

    <footer>
        <p>&copy; {{ now.Format "2006" }} {{ .Site.Title }}. {{ T "all_rights_reserved" }}</p>
        {{ block "footer_extra" . }}{{ end }} {{/* Блок для доп. контента в футере */}}
    </footer>

    {{ block "scripts_extra" . }}{{ end }} {{/* Блок для скриптов в конце body */}}
</body>
</html>

Разберем по частям:

  • {{ block "имя_блока" . }} ... {{ end }}: Так объявляется блок.
    • "имя_блока": Уникальное имя блока (например, “title”, “main”, “head_extra”).
    • .: Передаем текущий контекст (точку) внутрь блока. Это важно, чтобы в переопределенном блоке были доступны переменные вроде .Title, .Content и т.д.
    • Содержимое между {{ block ... }} и {{ end }} – это содержимое по умолчанию. Оно будет выведено, если более специфичный шаблон не переопределит этот блок.

Как теперь конкретные шаблоны (например, single.html) используют этот baseof.html?

Они просто определяют (define) содержимое для этих блоков.

Пример layouts/_default/single.html (для одиночной страницы):

{{/* Этот шаблон будет использовать layouts/_default/baseof.html */}}

{{ define "title" }}
    {{ .Title }} – {{ .Site.Title }} {{/* Переопределяем заголовок для одиночной страницы */}}
{{ end }}

{{ define "main" }}
    <article>
        <h2>{{ .Title }}</h2>
        {{ if .Params.subtitle }}
            <h3>{{ .Params.subtitle }}</h3>
        {{ end }}
        <p class="meta">Опубликовано: {{ .Date.Format "02 January 2006" }}
        {{ with .Params.author }} | Автор: {{ . }}{{ end }}
        </p>

        <div class="content">
            {{ .Content }} {{/* Основной контент страницы */}}
        </div>

        {{ if .Params.tags }}
            <div class="tags">
                <strong>Теги:</strong>
                {{ range .Params.tags }}
                    <a href="{{ "/tags/" | relLangURL }}{{ . | urlize }}">{{ . }}</a>
                {{ end }}
            </div>
        {{ end }}
    </article>
{{ end }}

{{ define "scripts_extra" }}
    {{/* Сюда можно добавить скрипты, нужные только для одиночных страниц */}}
    {{ end }}

Что происходит:

  1. Hugo видит, что для отображения одиночной страницы нужно использовать single.html.
  2. Он также видит, что существует baseof.html (например, в layouts/_default/).
  3. Он берет baseof.html за основу.
  4. Затем он ищет в single.html определения блоков ({{ define "имя_блока" }} ... {{ end }}).
  5. Если блок определен в single.html (например, {{ define "main" }}), то его содержимое вставляется вместо одноименного блока в baseof.html.
  6. Если какой-то блок из baseof.html не определен в single.html (например, мы не определили {{ define "footer_extra" }} в нашем single.html), то будет использовано содержимое по умолчанию из baseof.html.

Порядок поиска baseof.html (Lookup Order):

Hugo ищет baseof.html в определенном порядке, чтобы обеспечить максимальную гибкость:

  1. layouts/<TYPE>/<LAYOUT>-baseof.html (например, layouts/posts/single-baseof.html)
  2. layouts/<TYPE>/baseof.html (например, layouts/posts/baseof.html)
  3. layouts/_default/<LAYOUT>-baseof.html (например, layouts/_default/single-baseof.html)
  4. layouts/_default/baseof.html (самый общий)
  5. То же самое в папке темы, если используется тема.

Это означает, что ты можешь иметь один общий baseof.html для всего сайта, а потом, если нужно, переопределить его для конкретного типа контента (например, для постов блога сделать другой сайдбар) или даже для конкретного макета.

Использование baseof.html и блоков – это ключевой момент для создания поддерживаемых и чистых тем в Hugo. Один раз настроил каркас – и потом только наполняешь специфичные блоки. Красота!

Раз с базой разобрались, давай посмотрим, как эта база наполняется для разных типов страниц. Начнем со страниц, которые показывают списки чего-либо.

Списковые страницы (индексы, категории, теги и т.д.)

Раз с базой разобрались, давай посмотрим, как эта база наполняется для разных типов страниц. Начнем со страниц, которые показывают списки чего-либо.

Списковые страницы (List Pages) в Hugo – это страницы, которые отображают коллекцию других страниц. Это могут быть:

  • Главная страница сайта (Homepage): Отображает, например, последние посты.
  • Страницы разделов (Sections): Например, /posts/ (список всех постов) или /projects/ (список всех проектов). Создаются автоматически, если у тебя есть папки content/posts/ или content/projects/.
  • Страницы таксономий (Taxonomy Terms): Например, /categories/ (список всех категорий) или /tags/ (список всех тегов).
  • Страницы конкретных терминов таксономий (Taxonomy Term Lists): Например, /categories/технологии/ (список всех постов в категории “Технологии”) или /tags/hugo/ (список всех постов с тегом “hugo”).

Для всех этих типов страниц Hugo использует шаблоны списков.

Как Hugo выбирает шаблон для списковой страницы (Lookup Order):

Hugo – парень предсказуемый и ищет шаблоны в строгом порядке. Для списковых страниц он будет искать примерно так (упрощенно, для примера секции posts):

  1. layouts/section/posts.html (специфичный для секции “posts”)
  2. layouts/posts/list.html (тоже специфичный для “posts”, альтернативное имя)
  3. layouts/_default/list.html (дефолтный шаблон для всех списков, если ничего специфичного не найдено)
  4. layouts/_default/section.html (дефолтный шаблон для секций, если list.html нет)
  5. То же самое в папке активной темы, если ты ее используешь, причем шаблоны в корневом layouts/ имеют приоритет над шаблонами темы.

Для главной страницы есть свой порядок (index.html, home.html). Для таксономий – свой (например, layouts/taxonomy/tag.html для списка постов с тегом, layouts/taxonomy/tag.terms.html для списка всех тегов). Суть одна: от более специфичного к более общему.

Ключевые переменные и что с ними делать в list.html:

Внутри шаблона списковой страницы (например, layouts/_default/list.html), который, напомню, обычно расширяет baseof.html через {{ define "main" }}, тебе доступны:

  • .Title: Заголовок списка (например, “Posts” или “Категория: Технологии”).
  • .Permalink или .RelPermalink: Ссылка на эту списковую страницу.
  • .Data.Pages или просто .Pages: Коллекция страниц, принадлежащих этому списку. Это то, что мы будем перебирать.
    • Часто используют .Site.RegularPages (все обычные страницы сайта) или .Paginator.Pages (если используется пагинация).
  • .Paginator: Объект пагинатора, если ты настроил пагинацию для списка. Позволяет выводить ссылки “Следующая страница”, “Предыдущая страница” и т.д.
  • .Content: Если для этой списковой страницы есть файл _index.md (например, content/posts/_index.md), то его содержимое будет доступно тут. Это позволяет добавить описание или какой-то вводный текст на страницу раздела или таксономии.

Пример простого layouts/_default/list.html:

{{ define "title" }}
    {{ .Title }} - {{ .Site.Title }}
{{ end }}

{{ define "main" }}
    <div class="list-page">
        <h2>{{ .Title }}</h2>

        {{/* Если есть контент из _index.md, выводим его */}}
        {{ if .Content }}
            <div class="list-description">
                {{ .Content }}
            </div>
        {{ end }}

        {{/* Перебираем страницы в этом списке */}}
        {{ $pages := .Pages }}
        {{/* Если используем пагинацию, то берем страницы из пагинатора */}}
        {{ if .Paginator }}
            {{ $pages = .Paginator.Pages }}
        {{ end }}

        {{ if $pages }}
            <ul class="post-list">
                {{ range $pages }}
                    <li>
                        <h3><a href="{{ .RelPermalink }}">{{ .Title }}</a></h3>
                        <p class="meta">
                            {{ .Date.Format "02 Jan 2006" }}
                            {{ with .Params.author }} | Автор: {{ . }}{{ end }}
                        </p>
                        {{/* Можно вывести краткое содержание, если есть */}}
                        {{ .Summary }} 
                        {{ if .Truncated }}
                            <a href="{{ .RelPermalink }}">Читать дальше…</a>
                        {{ end }}
                    </li>
                {{ end }}
            </ul>
        {{ else }}
            <p>Здесь пока ничего нет.</p>
        {{ end }}

        {{/* Вывод пагинации, если она есть */}}
        {{ if .Paginator }}
            {{ template "_internal/pagination.html" . }}
        {{ end }}
    </div>
{{ end }}

Разберем интересные моменты:

  • {{ if .Content }}: Проверяем, есть ли у списковой страницы свой контент (из _index.md).
  • {{ $pages := .Pages }}: Присваиваем коллекцию страниц переменной $pages.
  • {{ if .Paginator }}: Если для этого списка настроена пагинация (обычно в hugo.toml), то .Paginator будет доступен. Мы берем страницы из .Paginator.Pages – это страницы для текущей “порции” пагинации.
  • {{ range $pages }}: Перебираем наши страницы. Внутри этого цикла точка . становится текущей страницей из списка, поэтому мы можем использовать {{ .RelPermalink }}, {{ .Title }}, {{ .Date }}, {{ .Summary }}.
  • .Summary: Hugo автоматически генерирует краткое содержание поста. По умолчанию это первые 70 слов контента. Можно настроить или указать разделитель `` в Markdown.
  • .Truncated: Булево значение, true если .Summary был усечен (т.е. есть что “читать дальше”).
  • {{ template "_internal/pagination.html" . }}: Hugo поставляется с внутренним шаблоном для пагинации. Его можно использовать, чтобы не писать логику кнопок “вперед/назад” самому. Ему нужно передать текущий контекст пагинатора (точку . в данном месте).

Про _index.md:

Для страниц секций (например, content/posts/) или страниц таксономий ты можешь создать файл _index.md (например, content/posts/_index.md). В этом файле ты можешь указать Front Matter (например, title, description для этой страницы списка) и написать какой-то контент, который будет выведен над списком постов.

Пример content/posts/_index.md:

---
title: "Все Записи Нашего Блога"
description: "Здесь собраны самые свежие и интересные статьи о всякой всячине."
# Можно даже указать свой layout для этой страницы списка, если надо
# layout: "my_custom_posts_list" 
---

Добро пожаловать в наш блог! Мы стараемся писать о технологиях, разработке и иногда о котиках.
Надеемся, вы найдете здесь что-то полезное для себя.

Этот title будет использован как {{ .Title }} на странице /posts/, а текст ниже – как {{ .Content }}.

Списковые страницы – это основа навигации по твоему сайту. Понимание того, как они работают и как их кастомизировать, очень важно.

Дальше посмотрим на антипода списковых страниц – на страницы, которые отображают одну-единственную единицу контента. То есть, на одиночные страницы.

Одиночные страницы (деталка поста/страницы)

Сейчас разберем, как показывать твой контент во всей красе – одну конкретную статью, новость или страницу “о себе, любимом”.

Одиночные страницы (Single Pages) – это, как ты уже догадался, страницы, которые отображают одну конкретную единицу контента. Это может быть:

  • Пост в блоге (content/posts/my-awesome-post.md)
  • Страница проекта (content/projects/super-duper-app.md)
  • Страница “О нас” (content/about.md)
  • Любой другой материал, который не является списком.

Именно на этих страницах твои пользователи будут проводить больше всего времени, читая то, что ты там наваял. Так что к их оформлению надо подойти с душой.

Как Hugo выбирает шаблон для одиночной страницы (Lookup Order):

Аналогично списковым страницам, Hugo ищет шаблон для одиночной страницы по строгому порядку. Допустим, у тебя есть пост content/posts/my-post.md (тип posts, секция posts). Hugo будет искать шаблон так:

  1. layouts/posts/my-post.html (шаблон специально для этого конкретного файла – используется редко, но возможность есть)
  2. layouts/posts/single.html (шаблон для всех одиночных страниц типа posts)
  3. layouts/_default/single.html (дефолтный шаблон для всех одиночных страниц, если ничего более специфичного не найдено)
  4. То же самое в папке активной темы, с тем же приоритетом корневых layouts/.

Обычно ты будешь работать либо с layouts/ТИП_КОНТЕНТА/single.html (если у тебя разные типы контента требуют сильно разного оформления), либо с общим layouts/_default/single.html.

Ключевые переменные, доступные в single.html:

Внутри single.html (который, как ты помнишь, обычно вставляет свое содержимое в блок main из baseof.html), точка . ссылается на текущую страницу, и тебе доступны все её атрибуты и содержимое:

  • .Title: Заголовок страницы из Front Matter.
  • .Content: Основное содержимое страницы, уже сконвертированное из Markdown в HTML. Это самое главное.
  • .Date: Дата публикации из Front Matter.
  • .Lastmod: Дата последнего изменения файла (если GitInfo включен, то из коммита, иначе – время модификации файла).
  • .PublishDate: Дата, с которой публикация разрешена.
  • .ExpiryDate: Дата, после которой публикация скрывается.
  • .Permalink / .RelPermalink: Абсолютная / относительная постоянная ссылка на страницу.
  • .Params: Объект со всеми твоими кастомными параметрами из Front Matter (.Params.имя_параметра).
  • .WordCount: Количество слов в .Content.
  • .ReadingTime: Примерное время чтения (в минутах).
  • .TableOfContents: HTML-код оглавления, сгенерированный из заголовков (##, ### и т.д.) твоего Markdown. Полезно для длинных статей. Глубину и заголовки можно настраивать в hugo.toml.
  • .Summary: Краткое содержание (на одиночных страницах тоже доступно, хотя чаще используется в списках).
  • .Keywords: Массив ключевых слов из Front Matter.
  • .Draft: true, если страница – черновик.
  • .Type: Тип контента (например, “posts”).
  • .Section: Секция, к которой принадлежит страница (например, “posts”).
  • .PrevInSection: Ссылка на предыдущую страницу в той же секции (по дате или весу).
  • .NextInSection: Ссылка на следующую страницу в той же секции.
  • .Prev: Предыдущая страница (глобально, по дате).
  • .Next: Следующая страница (глобально, по дате).
  • И еще куча всего. Смотри в доках Hugo по “Page Variables”.

Пример простого layouts/_default/single.html:

{{ define "title" }}
    {{ .Title }} – {{ .Site.Title }}
{{ end }}

{{ define "main" }}
    <article class="single-post">
        <header class="post-header">
            <h1>{{ .Title }}</h1>
            {{ with .Params.subtitle }}
                <p class="subtitle">{{ . }}</p>
            {{ end }}
            <div class="post-meta">
                <span>Опубликовано: {{ .Date.Format "02 January 2006" }}</span>
                {{ with .Params.author }}
                    <span> | Автор: {{ . }}</span>
                {{ end }}
                <span> | Время чтения: {{ .ReadingTime }} мин. ({{ .WordCount }} слов)</span>
            </div>
            {{ with .Params.featured_image }}
                <img src="{{ . | relURL }}" alt="{{ $.Title }}" class="featured-image">
            {{ end }}
        </header>

        {{ if .Site.Params.showTableOfContents | and .TableOfContents }} {{/* Показать оглавление, если включено в конфиге и есть что показывать */}}
            <aside class="table-of-contents">
                <h3>Оглавление:</h3>
                {{ .TableOfContents }}
            </aside>
        {{ end }}

        <div class="post-content">
            {{ .Content }}
        </div>

        {{ if .Params.tags }}
            <footer class="post-footer">
                <div class="tags">
                    <strong>Теги:</strong>
                    {{ range .Params.tags }}
                        <a href="{{ "/tags/" | relLangURL }}{{ . | urlize }}">{{ . }}</a>
                    {{ end }}
                </div>
            </footer>
        {{ end }}

        {{/* Навигация "Предыдущий/Следующий пост" в этой же секции */}}
        <nav class="post-navigation">
            {{ with .PrevInSection }}
                <a href="{{ .RelPermalink }}" class="prev-post">&laquo; Предыдущий: {{ .Title }}</a>
            {{ end }}
            {{ with .NextInSection }}
                <a href="{{ .RelPermalink }}" class="next-post">Следующий: {{ .Title }} &raquo;</a>
            {{ end }}
        </nav>

    </article>
{{ end }}

Что тут интересного:

  • {{ with .Params.subtitle }}: Элегантно выводим подзаголовок, только если он есть в Front Matter.
  • .Date.Format "02 January 2006": Форматируем дату по-человечески. Строка формата – это специфичный для Go способ указания формата (используется эталонная дата Mon Jan 2 15:04:05 MST 2006).
  • {{ .ReadingTime }} и {{ .WordCount }}: Полезная мелочь для читателей.
  • {{ .Params.featured_image | relURL }}: Если в Front Matter есть featured_image с путем к картинке, выводим ее. relURL делает URL относительным к корню сайта.
  • {{ if .Site.Params.showTableOfContents | and .TableOfContents }}: Оглавление показывается, только если в глобальном конфиге сайта (hugo.toml) есть параметр showTableOfContents = true и на текущей странице есть что включать в оглавление (.TableOfContents не пустое).
  • {{ .Content }}: Вот сюда и выводится весь твой отформатированный HTML из Markdown-файла.
  • Навигация {{ .PrevInSection }} и {{ .NextInSection }} позволяет легко переходить между статьями одного раздела.

Шаблон single.html – это то место, где ты можешь развернуться и сделать страницу своего контента максимально удобной и информативной. Тут можно добавлять комментарии (через Disqus, Staticman или другие сервисы), кнопки “поделиться”, информацию об авторе, связанные посты и многое другое.

В общем, как видишь, и списковые, и одиночные страницы строятся по схожим принципам: наследуют baseof.html и заполняют его блоки специфичным для себя контентом, используя доступные переменные страницы.

Теперь, когда мы знаем, как делать страницы целиком, самое время поговорить о маленьких переиспользуемых кусочках HTML, которые можно вставлять где угодно.

Partials (переиспользуемые куски кода)

Вижу, ты уже в нетерпении. Ну правильно, сейчас будем говорить о том, как не превращать свои шаблоны в спагетти-код, а делать их аккуратными и модульными. Речь пойдет о partials, или, как мы их в шутку тут обозначили, о “переиспользуемых кусках кода”. Иногда, конечно, если криво сделать, то и до “кусков говна” недалеко, но мы-то с тобой инженеры, сделаем как надо.

Partials (частичные шаблоны) – это небольшие, самостоятельные фрагменты HTML-кода (с логикой Go Templates, разумеется), которые ты можешь вставлять в другие, более крупные шаблоны (baseof.html, single.html, list.html и даже в другие partials).

На кой хрен они нужны?

  1. DRY, опять он, родимый. Если у тебя есть кусок кода, который повторяется на разных страницах или в разных частях сайта (например, шапка, подвал, навигационное меню, блок с иконками соцсетей, код Google Analytics), ты выносишь его в partial и вставляешь одной строкой там, где нужно. Изменил в одном месте – изменилось везде. Чистота, порядок, экономия времени.
  2. Организация и читаемость. Большие шаблоны становятся трудночитаемыми. Разбив их на логические partials, ты делаешь код более структурированным и понятным. Легче найти, что где лежит и за что отвечает.
  3. Компоненты. Partials – это, по сути, твои маленькие компоненты. Ты можешь сделать partial для карточки поста, для кнопки, для формы подписки – для чего угодно.

Где они лежат и как их создавать?

Все partials живут в папке layouts/partials/. Ты можешь создавать там подпапки для лучшей организации, если partials становится много. Например:

  • layouts/partials/header.html
  • layouts/partials/footer.html
  • layouts/partials/sidebar.html
  • layouts/partials/widgets/social-icons.html
  • layouts/partials/analytics/google-analytics.html

Имена файлов произвольные, но обычно отражают суть partial.

Как их вызывать (вставлять в другие шаблоны)?

Для этого есть две основные функции:

  1. {{ partial "имя_файла.html" . }}
  2. {{ partialCached "имя_файла.html" . "уникальный_ключ_кэширования" [аргументы_для_ключа] }}

Разберем partial:

  • "имя_файла.html": Путь к файлу partial относительно папки layouts/partials/. Если у тебя layouts/partials/widgets/social-icons.html, то вызывать будешь так: {{ partial "widgets/social-icons.html" . }}.
  • .: Это контекст (точка), который ты передаешь внутрь partial. Это очень важный момент!
    • Если ты передаешь . (текущий контекст), то внутри partial точка . будет такой же, как и в том месте, откуда ты его вызвал. То есть, если ты из single.html (где . это текущая страница) вызываешь {{ partial "my-partial.html" . }}, то в my-partial.html ты сможешь использовать {{ .Title }}, {{ .Content }}, {{ .Params }} и т.д., относящиеся к этой странице.
    • Ты можешь передать и другой контекст. Например, {{ partial "site-data.html" .Site }} – тогда внутри site-data.html точка . будет ссылаться на глобальный объект .Site.
    • Можно передать кастомный контекст, созданный с помощью функции dict:
      {{ $customData := dict "greeting" "Привет, инженер!" "pageTitle" .Title }}
      {{ partial "custom-greeting.html" $customData }}
      
      Тогда в custom-greeting.html ты сможешь обратиться к {{ .greeting }} и {{ .pageTitle }}.

Пример: Partial для иконок соцсетей

Создадим файл layouts/partials/social-icons.html:

{{/* layouts/partials/social-icons.html */}}
{{/* Ожидаем, что в .Site.Params есть секция social с нужными ссылками */}}
{{ with .Site.Params.social }}
    <div class="social-icons">
        {{ range $platform, $url := . }}
            {{ if $url }} {{/* Проверяем, что URL не пустой */}}
                <a href="{{ $url }}" target="_blank" rel="noopener noreferrer" aria-label="{{ $platform | humanize }}">
                    {{/* Здесь можно использовать SVG иконки или шрифтовые иконки */}}
                    {{/* Для простоты просто напишем название платформы */}}
                    <span class="icon-{{ $platform }}">{{ $platform | humanize }}</span>
                </a>
            {{ end }}
        {{ end }}
    </div>
{{ end }}

Предположим, в hugo.toml у тебя есть:

[params.social]
  twitter = "https://twitter.com/yourprofile"
  github = "https://github.com/yourprofile"
  linkedin = "https://linkedin.com/in/yourprofile"
  email = "mailto:you@example.com"

Теперь в layouts/_default/baseof.html (например, в футере) ты можешь вставить эти иконки:

{{/* ... остальной код baseof.html ... */}}
<footer>
    <p>&copy; {{ now.Format "2006" }} {{ .Site.Title }}</p>
    {{ partial "social-icons.html" . }} {{/* Передаем текущий контекст, из которого partial сможет взять .Site */}}
</footer>
{{/* ... */}}

Про partialCached:

Если у тебя есть partial, который генерирует один и тот же HTML для разных страниц (или для одной и той же страницы, но вызывается много раз) и не зависит от сильно меняющегося контекста, используй {{ partialCached }}. Он кэширует результат работы partial и отдает его из кэша при последующих вызовах с тем же ключом. Это может дать хороший прирост производительности на больших сайтах.

{{ partialCached "my-complex-widget.html" . .RelPermalink }}

Здесь .RelPermalink используется как часть ключа кэширования. Если виджет для каждой страницы свой, то кэш будет для каждой страницы. Если виджет один на весь сайт, можно использовать что-то вроде {{ partialCached "my-global-widget.html" .Site "global-widget-key" }}. Аргументы для ключа кэширования (variants) должны быть простыми типами.

Итого по partials:

  • Это твои строительные блоки.
  • Помогают не повторяться и структурировать код.
  • Внимательно следи за контекстом (.), который ты в них передаешь.
  • Используй partialCached для часто вызываемых и не слишком динамичных кусков.

Partials делают жизнь разработчика темы для Hugo гораздо проще и приятнее. Без них ты бы утонул в копипасте и сломал бы себе все пальцы, внося одинаковые правки в десятки файлов.

Ну что, теперь ты знаешь, как строить каркас (baseof.html), как наполнять его для списков (list.html) и одиночек (single.html), и как использовать маленькие кирпичики (partials). Осталось разобраться, какие данные тебе вообще доступны из коробки в этих шаблонах. Это про встроенные переменные.

Встроенные переменные Hugo (.Page, .Site – что доступно из коробки)

Под конец этого раздела про шаблоны давай разберем, какими боеприпасами тебя снабжает Hugo из коробки. То есть, какие встроенные переменные ты можешь использовать, чтобы творить свои шедевры.

Когда ты колдуешь в шаблоне, Hugo предоставляет тебе кучу полезной информации через так называемые “переменные”. Две самые главные, которые ты будешь использовать постоянно, это .Page и .Site. Ну и помни про “точку” (.) – часто она сама по себе является ссылкой на .Page (например, в single.html или list.html для самой списковой страницы).

1. .Site – Информация обо всем сайте

Объект .Site – это твой главный штаб, хранилище глобальной информации о сайте. Он доступен практически в любом месте шаблона. Что в нем полезного:

  • .Site.Title: Главный заголовок твоего сайта (из hugo.toml -> title).
  • .Site.BaseURL: Базовый URL сайта (из hugo.toml -> baseURL). Важно для построения правильных ссылок.
  • .Site.Params: Объект со всеми глобальными кастомными параметрами, которые ты задал в hugo.toml в секции [params]. Например, {{ .Site.Params.myGlobalSetting }}.
  • .Site.Copyright: Копирайт (из hugo.toml -> copyright).
  • .Site.LanguageCode: Код языка сайта (из hugo.toml -> languageCode).
  • .Site.Language: Объект с информацией о текущем языке (если сайт мультиязычный).
  • .Site.Menus: Доступ ко всем меню, определенным в hugo.toml или в Front Matter страниц. Например, {{ range .Site.Menus.main }}<li><a href="{{ .URL }}">{{ .Name }}</a></li>{{ end }}.
  • .Site.Pages: Коллекция всех страниц на сайте (включая посты, списки, таксономии и т.д.).
  • .Site.RegularPages: Более полезная коллекция – все “обычные” страницы, то есть те, у которых есть контент и которые не являются списками или таксономиями. Обычно это то, что ты хочешь вывести в списках “все посты”.
  • .Site.Sections: Список всех секций первого уровня (например, posts, projects).
  • .Site.Taxonomies: Объект со всеми таксономиями. Например, {{ range .Site.Taxonomies.tags }} чтобы перебрать все теги.
  • .Site.Data: Доступ к данным из папки data/. Если у тебя есть data/authors.yaml, то ты можешь получить к нему доступ через {{ .Site.Data.authors }}.
  • .Site.Lastmod (или .Site.LastChange в старых версиях): Дата последнего изменения на сайте (если Hugo может ее определить, обычно из Git).
  • .Site.Config: Доступ ко всей конфигурации сайта (включая params).
  • .Site.BuildDate: Время сборки сайта.

Короче, .Site – это твой глобальный справочник.

2. .Page – Информация о текущей странице

Объект .Page (или просто . в большинстве контекстов одиночных и списковых страниц) содержит всю информацию о текущей странице, которую Hugo в данный момент обрабатывает и рендерит.

Многие из этих полей мы уже упоминали, когда говорили про single.html и list.html, но давай их систематизируем:

  • Основные:

    • .Page.Title: Заголовок страницы.
    • .Page.Content: HTML-содержимое страницы (из Markdown).
    • .Page.Permalink: Абсолютная ссылка на страницу.
    • .Page.RelPermalink: Относительная ссылка на страницу (предпочтительнее для внутренних ссылок).
    • .Page.Date: Основная дата страницы (обычно дата публикации).
    • .Page.Lastmod: Дата последнего изменения.
    • .Page.PublishDate, .Page.ExpiryDate: Даты для управления видимостью.
    • .Page.Description: Описание (часто из Front Matter, полезно для SEO).
    • .Page.Keywords: Ключевые слова (массив).
    • .Page.Summary: Автоматически сгенерированное или вручную указанное краткое содержание.
    • .Page.Truncated: true, если .Summary был усечен.
  • Метаданные и параметры:

    • .Page.Params: Объект со всеми кастомными параметрами из Front Matter этой страницы. Например, {{ .Page.Params.featured_image }}.
    • .Page.Draft: true, если страница – черновик.
    • .Page.Type: Тип контента (например, posts). Определяется папкой или Front Matter.
    • .Page.Section: Секция, к которой принадлежит страница (например, posts).
    • .Page.Slug: Часть URL, если указана в Front Matter.
    • .Page.Weight: Вес для сортировки.
    • .Page.File: Информация о файле-источнике (.Page.File.Path, .Page.File.BaseFileName и т.д.).
  • Информация о контенте:

    • .Page.WordCount: Количество слов.
    • .Page.ReadingTime: Примерное время чтения.
    • .Page.TableOfContents: HTML-код оглавления.
  • Классификация и навигация (.Kind – очень важно!):

    • .Page.Kind: Строка, определяющая “тип” страницы с точки зрения Hugo. Может быть:

      • "page": Обычная контентная страница (например, пост блога, страница “О нас”).
      • "home": Главная страница сайта.
      • "section": Страница раздела (например, /posts/).
      • "taxonomy": Страница списка всех терминов одной таксономии (например, /tags/).
      • "taxonomyTerm": Страница списка контента для одного термина таксономии (например, /tags/hugo/).
      • "RSS", "sitemap", "robotsTXT", "404" - для соответствующих специфических файлов. Значение .Page.Kind очень полезно для написания условной логики в шаблонах, особенно в baseof.html или в partials, чтобы отображать разные элементы для разных типов страниц (например, хлебные крошки).
    • .Page.IsHome: true, если это главная страница (.Page.Kind == "home").

    • .Page.IsPage: true, если это обычная контентная страница (.Page.Kind == "page").

    • .Page.IsSection: true, если это страница раздела (.Page.Kind == "section").

    • .Page.Parent: Родительская страница (например, для поста это будет страница его секции).

    • .Page.Sections: Список дочерних секций (если текущая страница сама является секцией).

    • .Page.Pages: На списковых страницах (list.html, home.html) это коллекция дочерних страниц.

    • .Page.RegularPages: На списковых страницах коллекция дочерних “обычных” страниц.

    • .Page.Paginator: Объект пагинатора, если он используется для этой страницы.

    • .Page.Prev, .Page.Next: Предыдущая/следующая страница (глобально).

    • .Page.PrevInSection, .Page.NextInSection: Предыдущая/следующая страница в той же секции.

Это далеко не все, но это основной джентльменский набор, который тебе понадобится в 99% случаев. За полным списком и деталями всегда можно (и нужно!) обращаться к официальной документации Hugo по переменным (Page Variables и Site Variables). Она там довольно подробная.

Важно помнить: Содержимое .Page (и соответственно, просто .) меняется в зависимости от того, какую страницу Hugo рендерит. Внутри range .Site.RegularPages точка . будет ссылаться на очередную страницу из этого списка, а не на ту, на которой этот список выводится. Это основы работы с контекстом в Go Templates.

На этом с основами шаблонизации, пожалуй, всё. Ты теперь знаешь, из чего состоит тема Hugo, как работают шаблоны, какие данные в них доступны. Можно сказать, прошел курс молодого бойца по HTML-верстке под Hugo.

Как ощущения? Голова не кипит? Дальше по плану у нас конфигурация проекта, будем ковыряться в hugo.toml.

5. Конфигурация – рулим всем из одного места

Переходим к пульту управления твоего сайта – файлу конфигурации. Тут ты будешь задавать основные правила игры.

Если шаблоны – это лицо сайта, а контент – его душа, то файл конфигурации – это его мозг и нервная система. Здесь ты определяешь глобальные настройки, подключаешь темы, настраиваешь меню и многое другое.

Файл конфигурации (hugo.toml или config.toml/yaml/json) – сердце проекта.

Hugo очень гибкий в плане формата конфига. Раньше по умолчанию был config.toml, но сейчас Hugo активно продвигает hugo.toml как основной файл конфигурации. Ты также можешь использовать config.yaml или config.json, если тебе эти форматы ближе. Hugo сам разберется.

Мы будем ориентироваться на hugo.toml, так как это новый стандарт. Формат TOML (Tom’s Obvious, Minimal Language) довольно простой и читаемый, похож на старые добрые INI-файлы, но с возможностью вложенных структур.

  • Комментарии в TOML начинаются с #.
  • Строки заключаются в кавычки.
  • Массивы – ["раз", "два"].
  • Таблицы (объекты) – [имя_таблицы], вложенные таблицы [имя_таблицы.подимя].

Файл hugo.toml (или его аналог) лежит в корне твоего проекта.

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

  • config/_default/hugo.toml (базовая конфигурация)
  • config/production/hugo.toml (настройки для продакшена, переопределяют или дополняют базовые)
  • config/development/hugo.toml (настройки для локальной разработки)

Но для начала нам с головой хватит одного файла hugo.toml в корне.

Основные настройки: baseURL, title, languageCode, theme.

Давай посмотрим на самые первые и важные строки, которые должны быть в твоем hugo.toml:

# hugo.toml

baseURL = "https://example.com/"  # !!! Очень важно для правильных ссылок в продакшене
languageCode = "ru-RU"            # Язык твоего сайта
title = "Мой Охренительный Сайт на Hugo" # Главный заголовок сайта
theme = "имя_твоей_темы"          # Если используешь готовую тему

# Несколько других полезных настроек:
paginate = 10                     # Количество элементов на странице для списков с пагинацией
summaryLength = 70                # Количество слов в автоматически генерируемом .Summary
copyright = "© 2025 Суровый Архитектор. Все права защищены." # Копирайт для футера

enableRobotsTXT = true            # Сгенерировать robots.txt
enableGitInfo = false             # Использовать информацию из Git для .Lastmod и .GitInfo (может замедлять сборку)
defaultContentLanguage = "ru"     # Язык контента по умолчанию (для мультиязычных сайтов)

[params]
  # Сюда можно добавлять свои глобальные параметры, доступные через .Site.Params
  description = "Самый лучший сайт о всякой всячине, сделанный на Hugo."
  author = "Главный Инженер Проекта"

Разберем:

  • baseURL: Критически важная настройка. Это корневой URL твоего сайта, когда он будет в интернете. Hugo использует его для генерации всех абсолютных ссылок, RSS-фидов, карты сайта и т.д. Для локальной разработки (hugo server) это значение обычно переопределяется на http://localhost:1313/, но для сборки продакшена (hugo) оно должно быть правильным. Не забудь поменять https://example.com/ на свой реальный домен!
  • languageCode: Код языка в формате IETF BCP 47. Важно для SEO и доступности.
  • title: Отображается в теге <title> браузера, в заголовках и т.д.
  • theme: Если ты скачал готовую тему (например, с themes.gohugo.io) и положил ее в папку themes/, здесь ты указываешь имя папки этой темы. Hugo будет использовать ее шаблоны, ассеты и т.д. Если делаешь свою тему с нуля, эту строку можно пока закомментировать или убрать.
  • paginate: Стандартное количество элементов на страницах списков, где используется пагинация (.Paginator).
  • summaryLength: Длина автоматического резюме поста в словах.
  • copyright: Строка копирайта. Доступна через {{ .Site.Copyright }}.
  • enableRobotsTXT: Если true, Hugo сгенерирует базовый robots.txt. Его можно кастомизировать, создав файл layouts/robots.txt.
  • enableGitInfo: Если true и твой проект находится под Git, Hugo будет пытаться получить дату последнего изменения и другую информацию из Git-коммитов. Это может быть полезно для .Lastmod, но немного замедляет сборку.
  • [params]: Специальная секция для твоих собственных глобальных параметров. Все, что ты определишь здесь, будет доступно в шаблонах через {{ .Site.Params.имя_параметра }}. Очень удобно для настроек, которые не являются стандартными для Hugo, но нужны тебе на сайте (например, ID для аналитики, ссылки на соцсети по умолчанию и т.п.).

Это лишь малая часть того, что можно настроить. Полный список смотри в официальной документации Hugo “Configure Hugo”.

Настройка меню (главное, боковое, подвальное – какое хочешь).

Hugo позволяет легко определять навигационные меню прямо в hugo.toml. Ты можешь создать сколько угодно меню (main, footer, social, sidebar – называй как хочешь).

Синтаксис для определения меню в hugo.toml:

[menu] # Общая секция для всех меню

  [[menu.main]] # Двойные квадратные скобки означают массив элементов. Это определяет меню с именем "main".
    name = "Главная"       # Текст ссылки
    pageRef = "/"          # Ссылка на внутреннюю страницу (относительно content/)
                           # Hugo найдет страницу (например, _index.md или home.md) и возьмет ее .Permalink
    weight = 10            # Порядок. Меньше вес = раньше в меню.

  [[menu.main]]
    name = "Блог"
    pageRef = "/posts"     # Ссылка на раздел постов
    weight = 20

  [[menu.main]]
    identifier = "about_us" # Уникальный идентификатор (полезно для CSS или JS)
    name = "О Нас"
    url = "/about-me/"     # Можно использовать url для фиксированных ссылок (внешних или внутренних)
                           # pageRef более "умный", т.к. проверяет существование страницы
    weight = 30
    # pre = "<i class='fas fa-info-circle'></i> " # HTML перед текстом ссылки (если нужно)
    # post = " <span class='badge'>New</span>"     # HTML после текста ссылки

  [[menu.main]]
    name = "Проекты"
    pageRef = "/projects"
    weight = 25            # Пример, как вес влияет на порядок (Проекты будут между Блогом и О Нас)

  [[menu.main]]
    name = "Внешний Ресурс"
    url = "https://gohugo.io/"
    weight = 40
    [menu.main.params]     # Кастомные параметры для этого пункта меню
      target = "_blank"    # Например, чтобы открывать в новой вкладке
      rel = "noopener noreferrer"

  # Можно определять подменю, используя `parent`
  [[menu.main]]
    name = "Контакты"
    pageRef = "/contact"
    weight = 35

  [[menu.main]] # Подпункт для "Контактов"
    parent = "Контакты" # Указываем имя родительского пункта (или его identifier)
    name = "Почта"
    url = "mailto:test@example.com"
    weight = 1

  # Другое меню, например, для футера
  [[menu.footer]]
    name = "Политика Конфиденциальности"
    pageRef = "/privacy"
    weight = 10

Ключевые поля для пунктов меню:

  • name: Текст, который увидит пользователь.
  • pageRef: Путь к контент-файлу (относительно content/) или секции. Hugo автоматически определит правильный Permalink. Самый надежный способ для внутренних ссылок.
  • url: Прямой URL. Используй для внешних ссылок или для внутренних, если pageRef по какой-то причине не подходит.
  • weight: Целое число, определяющее порядок. Элементы с меньшим весом идут первыми.
  • identifier: Уникальный строковый идентификатор. Полезен, если на него нужно ссылаться (например, для parent или для CSS/JS).
  • parent: identifier или name родительского пункта меню, если это подменю.
  • pre / post: HTML-код, который будет добавлен до/после текста ссылки.
  • [menu.имя_меню.params]: Свои параметры для пункта меню. В примере выше мы использовали target = "_blank" для открытия ссылки в новой вкладке. Доступны в шаблоне через {{ .Params.target }} для пункта меню.

В шаблоне ты можешь вывести меню так (пример для меню main):

<nav>
    <ul>
        {{ range .Site.Menus.main }}
            <li>
                <a href="{{ .URL }}" {{ with .Params.target }}target="{{ . }}"{{ end }} {{ with .Params.rel }}rel="{{ . }}"{{ end }}>
                    {{- .Pre -}}
                    {{ .Name }}
                    {{- .Post -}}
                </a>
                {{ if .HasChildren }} {{/* Проверяем, есть ли подменю */}}
                    <ul>
                        {{ range .Children }}
                            <li><a href="{{ .URL }}">{{ .Name }}</a></li>
                        {{ end }}
                    </ul>
                {{ end }}
            </li>
        {{ end }}
    </ul>
</nav>

Это базовый пример, ты можешь его усложнять, добавляя классы, проверяя активные пункты и т.д.

Переменные окружения для разных конфигураций (дев, прод).

Иногда тебе нужно, чтобы сайт вел себя по-разному в зависимости от того, где он собирается: на твоей локальной машине для разработки или на сервере для продакшена. Например, другой baseURL, другой код Google Analytics или отключение каких-то дебаг-фич на проде.

Hugo это понимает. Есть несколько способов:

  1. Переменные окружения ОС: Некоторые параметры Hugo можно задать через переменные окружения. Они обычно имеют префикс HUGO_. Например:

    • HUGO_BASEURL="https://prod.example.com/"
    • HUGO_THEME="my-prod-theme"
    • HUGO_ENV="production" (или development) – эта переменная важна.

    Hugo автоматически подхватит эти переменные.

  2. Конфигурационные директории (предпочтительный способ для сложных случаев): Как я уже упоминал, ты можешь создать папку config/ в корне проекта. В ней:

    • config/_default/hugo.toml (или params.toml, menus.toml, languages.toml – можно разбить конфиг на несколько файлов) – это базовые настройки.
    • config/development/hugo.toml – настройки для окружения development.
    • config/production/hugo.toml – настройки для окружения production.

    Когда Hugo собирает сайт, он определяет текущее окружение:

    • hugo server по умолчанию запускается в окружении development.
    • hugo (без server) по умолчанию собирает для окружения production.
    • Ты можешь явно указать окружение: hugo --environment staging или установив переменную HUGO_ENV=staging.

    Hugo сначала читает конфиг из _default/, а затем применяет поверх него конфиг из папки текущего окружения (например, production/). Настройки из специфичного окружения переопределяют или дополняют базовые.

    Пример:

    • config/_default/hugo.toml:
      title = "Мой Сайт"
      [params]
        showDrafts = false
      
    • config/development/hugo.toml:
      baseURL = "http://localhost:1313/"
      buildDrafts = true # Чтобы видеть черновики на локальном сервере
      [params]
        showDrafts = true # Переопределяем параметр
        debugInfo = true  # Добавляем новый параметр для разработки
      
    • config/production/hugo.toml:
      baseURL = "https://mysuperprod.com/"
      # buildDrafts здесь не указан, значит, будет false по умолчанию для прода
      [params]
        # showDrafts не указан, значит, будет false из _default
        googleAnalytics = "UA-12345678-1"
      

    Это очень мощный механизм для управления конфигурациями.

  3. Функция getenv в шаблонах: Ты можешь напрямую из шаблона получить значение переменной окружения ОС:

    {{ $envVar := getenv "MY_CUSTOM_ENV_VARIABLE" }}
    {{ if $envVar }}
        <p>Переменная окружения: {{ $envVar }}</p>
    {{ end }}
    

    Но для конфигурации лучше использовать файлы, это чище.

С конфигурацией вроде бы всё основное проговорили. Это важный раздел, потому что именно hugo.toml (или его аналоги) задает тон всему проекту. Понимание этих настроек сэкономит тебе кучу времени и нервов.

Как оно? Готов двигаться к следующей большой теме – расширенным возможностям Hugo? Там про шорткоды, Hugo Pipes и прочие интересные штуки.

6. Расширенные возможности

Переходим к фичам, которые делают из Hugo не просто генератор статики, а настоящий швейцарский нож для веб-разработчика. Здесь будет про то, как выйти за рамки обычного Markdown и заставить Hugo делать реально крутые штуки.

Держись крепче, сейчас будем погружаться в магию Hugo.

Shortcodes (макросы для контента, если Markdown уже не хватает)

Markdown – это, конечно, хорошо: просто, чисто, понятно. Но что делать, если тебе в контент надо вставить что-то посложнее, чем просто текст, картинку или список? Например, видео с YouTube, интерактивную карту, кастомный блок с предупреждением, галерею изображений или даже спойлер? Копипастить куски HTML прямо в Markdown – это моветон и прямой путь к помойке в контенте.

Вот тут на сцену выходят шорткоды (shortcodes).

Что это за звери? Шорткоды – это, по сути, твои кастомные HTML-теги, которые ты можешь использовать прямо в .md файлах. Hugo находит эти теги при сборке сайта и заменяет их на HTML-код, сгенерированный специальным шаблоном этого шорткода. Это как макросы, если тебе так понятнее. Они позволяют тебе инкапсулировать сложную разметку и логику в переиспользуемый компонент, сохраняя при этом чистоту Markdown-файлов.

Где живут и как создаются? Шаблоны для шорткодов – это обычные HTML-файлы с логикой Go Templates, и лежат они в папке layouts/shortcodes/. Имя файла (без .html) становится именем твоего шорткода. Например, layouts/shortcodes/myalert.html создаст шорткод {​{< myalert >}}.

Типы шорткодов:

  1. Простой (без параметров и внутреннего содержимого):

    • В Markdown: {​{< mysimplecode >}}
    • Шаблон (layouts/shortcodes/mysimplecode.html):
      <p>Это мой простой шорткод!</p>
      
  2. С параметрами: Параметры могут быть именованными или позиционными.

    • Именованные:
      • В Markdown: {​{< image src="/images/cat.jpg" alt="Милый котик" caption="Котик дня" >}}
      • Шаблон (layouts/shortcodes/image.html):
        <figure>
            <img src="{{ .Get "src" }}" alt="{{ .Get "alt" }}">
            {{ with .Get "caption" }}<figcaption>{{ . }}</figcaption>{{ end }}
        </figure>
        
        Внутри шаблона шорткода параметры доступны через метод .Get "имя_параметра".
    • Позиционные:
      • В Markdown: {​{< video "youtube" "ABCdEFGhI123" >}}
      • Шаблон (layouts/shortcodes/video.html):
        {{ $platform := .Get 0 }}
        {{ $id := .Get 1 }}
        {{ if eq $platform "youtube" }}
            <div class="embed-responsive embed-responsive-16by9">
                <iframe src="https://www.youtube.com/embed/{{ $id }}" frameborder="0" allowfullscreen></iframe>
            </div>
        {{ else if eq $platform "vimeo" }}
            <div class="embed-responsive embed-responsive-16by9">
                <iframe src="https://player.vimeo.com/video/{{ $id }}" frameborder="0" allowfullscreen></iframe>
            </div>
        {{ end }}
        
        Позиционные параметры доступны через .Get N, где N – это индекс параметра (начиная с 0).
  3. Парный (enclosing, с внутренним содержимым): Эти шорткоды имеют открывающий и закрывающий тег, а контент между ними доступен в шаблоне шорткода через переменную .Inner.

    • В Markdown:
      {​{< alert type="warning" title="Внимание!" >}}
      Это **очень важное** сообщение. Обратите на него внимание.
      * Пункт 1
      * Пункт 2
      {​{< /alert >}}
      
    • Шаблон (layouts/shortcodes/alert.html):
      {{ $type := .Get "type" | default "info" }}
      {{ $title := .Get "title" }}
      <div class="alert alert-{{ $type }}">
          {{ with $title }}<h4>{{ . }}</h4>{{ end }}
          {{ .Inner | markdownify }} {{/* .Inner содержит всё, что между тегами шорткода */}}
                                    {{/* markdownify обрабатывает внутренний контент как Markdown */}}
      </div>
      
      Ключевой момент здесь – {{ .Inner | markdownify }}. Переменная .Inner содержит текст “как есть”. Если ты хочешь, чтобы Markdown внутри шорткода тоже отрендерился в HTML, ты пропускаешь .Inner через функцию markdownify. Если этого не сделать, HTML-теги, например, будут отображены как текст.

Доступ к контексту страницы и сайта из шорткода: Внутри шаблона шорткода обычная точка . ссылается на специальный объект шорткода (через который мы получаем параметры с помощью .Get или внутреннее содержимое через .Inner). Чтобы получить доступ к переменным текущей страницы (той, в Markdown которой вставлен шорткод), используй .Page:

  • {{ .Page.Title }}
  • {{ .Page.Permalink }}
  • {{ .Page.Params.какой_то_параметр_страницы }}

А к глобальным переменным сайта – через .Page.Site:

  • {{ .Page.Site.Title }}
  • {{ .Page.Site.Params.глобальный_параметр }}

Встроенные шорткоды Hugo: Hugo поставляется с набором полезных встроенных шорткодов. Тебе не нужно для них создавать шаблоны, они уже есть. Некоторые из них:

  • figure: Для вставки изображений с подписями и атрибутами. Очень гибкий.
    Альтернативный текст
    Заголовок картинки

    Это подпись под картинкой.

  • ref и relref: Для создания надежных внутренних ссылок на другие страницы или секции. Они проверяют существование ссылки на этапе сборки. Если ссылка битая – Hugo выдаст ошибку. Это мега-полезно.
    Прочтите нашу статью о [котиках]({​{< ref "posts/o-kotikah.md" >}}).
    Или посмотрите [относительную ссылку]({​{< relref "other-post.md" >}}).
    
    ref генерирует абсолютный URL (с baseURL), relref – относительный. Лучше всегда использовать их для внутренних ссылок вместо обычных Markdown-ссылок, особенно если структура сайта может меняться.
  • youtube, vimeo: Для вставки видео.
  • gist: Для вставки GitHub Gists.
  • highlight: Для подсветки синтаксиса блока кода (хотя обычно для этого используют Markdown fences ).
  • param: Для вывода значения параметра из Front Matter текущей страницы.
    Автор этой статьи: {​{< param "author" >}}
    

Изучи список встроенных шорткодов в документации Hugo, многие из них могут тебе пригодиться.

Нюансы и лучшие практики:

  • Не переусердствуй. Шорткоды – это мощно, но если ты будешь пихать их в каждую строку, твой Markdown станет трудночитаемым и менее портативным (если вдруг решишь переехать с Hugo на что-то другое).
  • Давай шорткодам понятные имена.
  • Документируй свои кастомные шорткоды: какие параметры они принимают и что делают. Хотя бы для себя.
  • Используй {{- ... -}} для контроля пробелов. Как и в обычных шаблонах, дефисы - рядом с фигурными скобками в шаблонах шорткодов могут убирать лишние пробелы и переводы строк в генерируемом HTML, делая его чище.

Шорткоды – это один из столпов, на которых держится гибкость Hugo в работе с контентом. Они позволяют тебе, как инженеру, дать авторам контента (даже если это ты сам) удобные инструменты для создания богатых и сложных страниц без необходимости лезть в HTML/CSS/JS каждый раз.

Ну как, впечатляет мощь? Дальше у нас на очереди – как подгружать внешние данные в твои шаблоны, то есть про Data Templates.

Data Templates (грузим данные из JSON, YAML, CSV – откуда угодно)

Шорткоды – это хорошо, но иногда тебе нужно работать не с кусками HTML, а с настоящими данными: списками, таблицами, объектами. Может, у тебя есть прайс-лист в CSV, список авторов в YAML или какие-то конфигурационные данные в JSON, которые ты не хочешь хардкодить в шаблонах или пихать в основной hugo.toml.

Вот тут-то Hugo и показывает свою гибкость с помощью Data Files (файлов данных) и Data Templates (хотя правильнее их называть просто “использование данных в шаблонах”).

Что это такое и зачем нужно?

Hugo позволяет тебе положить файлы с данными в специальную папку data/ в корне твоего проекта. При сборке сайта Hugo автоматически читает эти файлы, парсит их и делает доступными в твоих шаблонах через объект .Site.Data.

Преимущества такого подхода:

  1. Разделение данных и представления: Ты можешь хранить свои данные (например, список сотрудников, характеристики товаров, пункты меню для сложной навигации) отдельно от HTML-разметки. Это чище и проще в поддержке.
  2. Управление данными: Данные в стандартизированных форматах (JSON, YAML, TOML, CSV) легче редактировать, в том числе людям, далеким от HTML. Иногда проще попросить кого-то поправить строчку в CSV, чем лезть в шаблон.
  3. Переиспользование данных: Одни и те же данные можно использовать на разных страницах и в разных частях сайта.
  4. Работа с внешними источниками (относительно): Ты можешь легко подложить данные, экспортированные из других систем.

Где лежат файлы данных и какие форматы поддерживаются?

  • Все файлы данных должны находиться в директории data/ в корне твоего Hugo-проекта.
  • Ты можешь создавать поддиректории внутри data/ для лучшей организации. Например, data/team/members.json или data/pricing/plans.csv.
  • Hugo “из коробки” понимает следующие форматы:
    • JSON (.json)
    • YAML (.yaml или .yml)
    • TOML (.toml)
    • CSV (.csv): Для CSV есть нюанс – первая строка файла по умолчанию считается заголовком (именами полей).

Как получить доступ к данным в шаблонах?

Все загруженные данные доступны через глобальный объект .Site.Data. Структура этого объекта соответствует структуре твоей папки data/ и именам файлов (без расширения).

  • Если у тебя есть файл data/authors.yaml, ты обращаешься к нему так: {{ .Site.Data.authors }}.
  • Если файл лежит в подпапке, например, data/products/featured.json, то доступ будет таким: {{ .Site.Data.products.featured }}.
  • Если имя файла содержит дефисы, например data/site-settings.toml, то в шаблоне ты можешь использовать index для доступа: {{ index .Site.Data "site-settings" }} или, если версия Hugo позволяет, иногда работает и прямой доступ с кавычками, но index надежнее для имен с спецсимволами. Для имен без спецсимволов {{ .Site.Data.sitesettings }} (Hugo пытается привести к camelCase или lower) тоже может сработать, но лучше использовать имена файлов, удобные для прямого маппинга. Проще всего использовать имена файлов без дефисов и пробелов, например data/siteSettings.toml -> {{ .Site.Data.siteSettings }}.

Пример использования:

Допустим, у нас есть список авторов в файле data/team/authors.yaml:

# data/team/authors.yaml
- id: "jdoe"
  name: "Иван Иванов"
  email: "ivan@example.com"
  bio: "Главный спец по Hugo, пишет код и документацию."
  social:
    twitter: "ivanov_hugo"
    github: "johndoe_coder"
- id: "psidorov"
  name: "Петр Сидоров"
  email: "petr@example.com"
  bio: "Эксперт по Markdown и созданию контента."
  social:
    linkedin: "petrsidorov"

Теперь в любом шаблоне (например, в list.html или в partial) ты можешь вывести этот список:

<h3>Наша Команда Авторов:</h3>
<ul class="author-list">
    {{ range .Site.Data.team.authors }} {{/* Обращаемся к data/team/authors.yaml */}}
        <li class="author-item" id="author-{{ .id }}">
            <h4>{{ .name }}</h4>
            <p>Email: <a href="mailto:{{ .email }}">{{ .email }}</a></p>
            <p>{{ .bio }}</p>
            {{ with .social }} {{/* Если есть секция social */}}
                <p>Соцсети:
                    {{ with .twitter }}<a href="https://twitter.com/{{ . }}">Twitter</a> {{ end }}
                    {{ with .github }}<a href="https://github.com/{{ . }}">GitHub</a> {{ end }}
                    {{ with .linkedin }}<a href="https://linkedin.com/in/{{ . }}">LinkedIn</a> {{ end }}
                </p>
            {{ end }}
        </li>
    {{ else }}
        <li>Информации об авторах пока нет.</li>
    {{ end }}
</ul>

В этом примере мы перебираем массив авторов (range .Site.Data.team.authors). Внутри цикла точка . ссылается на текущий элемент массива (объект автора), и мы можем обращаться к его полям (.id, .name, .bio, .social.twitter).

Если у тебя CSV файл, например, data/pricing/plans.csv:

id,planName,price,features
basic,"Базовый",10,"10 ГБ диск, 1000 email, Поддержка 24/7"
premium,"Премиум",25,"50 ГБ диск, 5000 email, Приоритетная поддержка, API доступ"
ultra,"Ультра",50,"200 ГБ диск, Безлимит email, VIP поддержка, Персональный менеджер"

В шаблоне (Hugo > 0.41 для CSV):

<table>
    <thead>
        <tr>
            <th>План</th>
            <th>Цена ($/мес)</th>
            <th>Возможности</th>
        </tr>
    </thead>
    <tbody>
        {{ range .Site.Data.pricing.plans }} {{/* data/pricing/plans.csv */}}
            <tr>
                <td>{{ .planName }}</td>      {{/* Имена полей берутся из первой строки CSV */}}
                <td>{{ .price }}</td>
                <td>
                    <ul>
                    {{ range $feature := split .features "," }} {{/* Разбираем строку features по запятой */}}
                        <li>{{ trim $feature " " }}</li>    {{/* Убираем лишние пробелы */}}
                    {{ end }}
                    </ul>
                </td>
            </tr>
        {{ end }}
    </tbody>
</table>

Hugo парсит CSV в массив объектов (или мап), где ключи – это заголовки из первой строки.

Получение данных из удаленных источников (на этапе сборки!):

Hugo также умеет подгружать данные из внешних URL во время сборки сайта с помощью функций getJSON и getCSV. Это не делает сайт динамическим в реальном времени, данные загружаются один раз при каждой сборке сайта.

  • {{ $jsonData := getJSON "https://api.example.com/some/data.json" }}
  • {{ $csvData := getCSV "," "https://example.com/some/data.csv" }} (первый аргумент – разделитель)

Пример с getJSON:

{{ $apiResponse := getJSON "https://api.github.com/users/gohugoio" }}
{{ if $apiResponse }}
    <p>У Hugo на GitHub {{ $apiResponse.public_repos }} публичных репозиториев.</p>
    <p>Описание: {{ $apiResponse.bio }}</p>
{{ else }}
    <p>Не удалось загрузить данные с GitHub API.</p>
{{ end }}

Важно про удаленные данные:

  • Загрузка происходит при каждой сборке (hugo или hugo server). Если API медленный или часто недоступен, это замедлит сборку.
  • Если API требует аутентификации, тебе придется передавать токены/ключи (например, через заголовки, если getJSON это поддерживает в твоей версии Hugo, или через переменные окружения и предварительную загрузку скриптом).
  • Для часто меняющихся данных этот подход не годится – сайт останется статическим между сборками. Для “живых” данных нужен JavaScript на клиенте, который будет их подгружать.
  • Hugo кэширует ответы от getJSON и getCSV чтобы ускорить повторные сборки. Папку кэша ($TMPDIR/hugo_cache/) можно очистить, если нужно принудительно обновить данные.

Итог по Data Files: Это чертовски удобный способ отделить данные от логики представления и сделать сайт более гибким и управляемым. Особенно полезно для всяких списков, таблиц, конфигурационных блоков, которые могут часто меняться или которыми должны управлять не только разработчики. Используй это с умом, и твои шаблоны станут чище, а жизнь – проще.

Дальше по плану у нас одна из самых мощных штук в Hugo для работы с фронтендом – Hugo Pipes, или Asset Pipeline. Готов к тому, как Hugo может сам собирать и оптимизировать твои CSS, JS и картинки?

Asset Pipeline (Hugo Pipes) – обработка CSS/JS, картинок

Так, инженер, пристегни ремни. Сейчас мы окунемся в то, что реально отличает Hugo от многих других генераторов и делает его маленькой сборочной фабрикой для твоего фронтенда. Речь пойдет про Hugo Pipes, или, как его еще называют, Asset Pipeline.

Это встроенный в Hugo механизм для обработки твоих “ассетов” – CSS, JavaScript, изображений и других файлов – прямо во время сборки сайта. И что самое приятное, для многих типовых задач тебе больше не нужны будут внешние монстры типа Webpack, Gulp или Grunt (хотя Hugo может и с ними дружить, если очень хочется).

Зачем это вообще сдалось?

  1. Простота сборки: Не надо настраивать отдельный Node.js-стек для компиляции SASS, минификации JS или оптимизации картинок. Hugo делает это сам. Один инструмент для всего.
  2. Скорость: Hugo Pipes работает быстро. Операции выполняются на лету, результаты могут кэшироваться.
  3. Удобство: Логика обработки ассетов живет прямо в твоих шаблонах, рядом с HTML-разметкой, которая их использует.
  4. Фингерпринтинг (Cache Busting): Hugo может автоматически добавлять хеш к именам файлов ассетов (например, style.a34df5.css). Это гарантирует, что браузер пользователя загрузит новую версию файла, когда он изменится, а не будет использовать старую из кэша.

Как это работает: объект resources и его волшебные методы

  • Где лежат исходники: Файлы, которые ты собираешься обрабатывать через Hugo Pipes, должны лежать в папке assets/ твоего проекта (а не в static/, как файлы, которые не требуют обработки). Например, assets/scss/main.scss или assets/js/app.js.

  • Получение ресурса: Первый шаг – получить ресурс в шаблоне с помощью resources.Get:

    {{ $scss := resources.Get "scss/main.scss" }}
    {{ $js := resources.Get "js/app.js" }}
    {{ $image := resources.Get "images/my-photo.jpg" }}
    

    Это создает специальный объект “ресурс”, с которым дальше можно работать.

  • Цепочки обработки (Pipes): Дальше ты пропускаешь этот ресурс через цепочку функций-обработчиков, используя знакомый нам оператор |.

Обработка CSS (SASS/SCSS):

  1. Компиляция SASS/SCSS в CSS: (Требуется extended версия Hugo)

    {{ $style := resources.Get "scss/main.scss" | toCSS }}
    

    Переменная $style теперь содержит объект скомпилированного CSS.

  2. Опции для toCSS: Можно передать параметры, например, для минификации:

    {{ $options := dict "targetPath" "css/style.css" "outputStyle" "compressed" }}
    {{ $style := resources.Get "scss/main.scss" | toCSS $options }}
    
    • targetPath: Имя выходного файла (относительно public/). Не обязательно, но полезно для отладки.
    • outputStyle: expanded (читаемый) или compressed (минифицированный).
  3. PostCSS: Если тебе нужен PostCSS (например, для автопрефиксера), это тоже можно:

    {{ $style := resources.Get "scss/main.scss" | toCSS | postCSS }}
    

    Для этого у тебя должен быть установлен postcss-cli и нужные плагины (например, autoprefixer) глобально или локально в node_modules твоего проекта, а также файл конфигурации postcss.config.js в корне проекта.

  4. Минификация CSS (если еще не сделано):

    {{ $style := $style | minify }}
    

Обработка JavaScript:

  1. ESBuild (современный и рекомендуемый способ для сборки и минификации JS): Hugo использует мощный сборщик ESBuild. Он умеет бандлить модули, транспилировать современный JS и минифицировать.

    {{ $js := resources.Get "js/app.js" | js.Build (dict "targetPath" "bundle.js" "minify" true "bundle" true "format" "iife" "sourcemap" "inline") }}
    
    • targetPath: Имя выходного файла.
    • minify: true для минификации.
    • bundle: true для сборки всех импортов в один файл.
    • format: Формат вывода (iife, esm, cjs). iife (Immediately Invoked Function Expression) – хороший выбор для браузерных скриптов, подключаемых через <script>.
    • sourcemap: inline или external для генерации карт кода.
  2. Babel: Если у тебя уже есть настроенный Babel (babel.config.js и зависимости в node_modules), Hugo может его использовать (хотя js.Build с ESBuild обычно предпочтительнее для новых проектов).

    {{ $js := resources.Get "js/legacy.js" | babel (dict "minify" true) }}
    
  3. Минификация JS (если еще не сделано):

    {{ $js := $js | minify }}
    

Общие операции с ресурсами:

  1. Фингерпринтинг (для Cache Busting): Эта операция добавляет хеш к имени файла.

    {{ $style := resources.Get "scss/main.scss" | toCSS (dict "outputStyle" "compressed") | fingerprint "sha256" }}
    {{ $js := resources.Get "js/app.js" | js.Build (dict "minify" true "bundle" true) | fingerprint }}
    

    Теперь $style.RelPermalink будет что-то вроде /css/main.a34df56e.css.

  2. Конкатенация (объединение) файлов:

    {{ $jsLibs := resources.Get "js/lib1.js" }}
    {{ $jsApp := resources.Get "js/my-app-code.js" }}
    {{ $jsBundle := slice $jsLibs $jsApp | resources.Concat "js/final-bundle.js" }}
    {{ $jsBundle = $jsBundle | js.Build (dict "minify" true) | fingerprint }}
    
  3. Публикация ресурсов и использование в HTML: Обработанные ресурсы не копируются в public/ автоматически. Они публикуются только тогда, когда ты используешь их .Permalink или .RelPermalink в шаблоне:

    <link rel="stylesheet" href="{{ $style.RelPermalink }}">
    <script src="{{ $js.RelPermalink }}" defer></script>
    

    Hugo сам позаботится о том, чтобы эти файлы (main.a34df56e.css, bundle.b23cf67a.js) оказались в нужных местах в папке public/.

  4. Свойства объекта ресурса: У обработанного ресурса есть полезные свойства:

    • .Permalink: Абсолютный URL к ресурсу.
    • .RelPermalink: Относительный URL (обычно его и используют).
    • .MediaType: MIME-тип ресурса (например, text/css, application/javascript).
    • .Content: Содержимое ресурса в виде строки (если нужно его встроить прямо в HTML, например, для критического CSS).
    • .Data: Метаданные о ресурсе.
    • .Integrity: Хеш для Subresource Integrity (SRI), если использовался fingerprint. {{ $style.Data.Integrity }}.

Обработка изображений (кратко, т.к. это и отдельная тема): Hugo Pipes также позволяет обрабатывать изображения из папки assets/.

{{ $original := resources.Get "images/big-photo.jpg" }}
{{ $resized := $original.Resize "800x q90 Lanczos" }} {{/* Ресайз до 800px по ширине, качество 90%, метод Lanczos */}}
{{ $webpThumb := $resized.Process "webp" }} {{/* Конвертация в WebP */}}
<img src="{{ $webpThumb.RelPermalink }}" width="{{ $webpThumb.Width }}" height="{{ $webpThumb.Height }}" alt="Крутая картинка">

Тут куча возможностей: Resize, Fit (вписать в размеры с сохранением пропорций), Fill (заполнить с обрезкой), конвертация форматов (.Process "webp"), фильтры и т.д.

static/ vs assets/ – когда что использовать?

  • static/: Для файлов, которые не требуют никакой обработки Hugo и должны быть скопированы в public/ “как есть”. Например:
    • favicon.ico
    • robots.txt (если ты не генерируешь его через Hugo)
    • Предварительно собранные библиотеки JS/CSS, которые ты не хочешь трогать.
    • Шрифты, PDF-файлы и т.п.
  • assets/: Для всех файлов, которые ты хочешь пропустить через Hugo Pipes:
    • SASS/SCSS файлы.
    • JavaScript, который нужно бандлить, транспилировать или минифицировать.
    • Изображения, которые ты будешь ресайзить, оптимизировать или конвертировать.
    • Любые другие файлы, к которым ты хочешь применить resources.Get и дальнейшую обработку.

Конфигурация Hugo Pipes: Некоторые глобальные настройки для Hugo Pipes можно задать в hugo.toml. Например, указать путь к postcss.config.js или настроить параметры сборки JS по умолчанию.

# hugo.toml
[build]
  writeStats = true # Создает файл hugo_stats.json с информацией о сборке ассетов, полезно для анализа

[ postcss ]
  # Необязательно, если postcss.config.js в корне
  # configFile = 'path/to/your/postcss.config.js'

# Глобальные опции для js.Build можно тоже задавать, но это реже нужно

Hugo Pipes – это невероятно мощный инструмент. Он позволяет держать всю логику сборки фронтенда внутри Hugo, избавляя от необходимости во внешних инструментах для большинства повседневных задач. Освоив его, ты сможешь делать современные, быстрые и хорошо оптимизированные сайты с минимальными усилиями.

Это был серьезный кусок информации. Как ты там, инженер? Переварил? Дальше у нас мультиязычность – тоже интересная тема, если вдруг твой сайт должен говорить на нескольких языках.

Мультиязычность (если вдруг приспичило)

Да, инженер, Hugo из коробки умеет делать мультиязычные сайты. И делает это довольно элегантно. Если тебе нужно, чтобы твой контент был доступен, скажем, на русском, английском и китайском – Hugo тебе в этом поможет, и не придется городить огород из нескольких отдельных сайтов или костыльных плагинов, как в некоторых CMS.

Зачем это нужно? Ну, очевидно – чтобы расширить аудиторию. Плюс, поисковики любят, когда контент правильно размечен для разных языков, это хорошо для SEO.

Основные принципы и конфигурация в hugo.toml:

Вся магия начинается с настройки языков в твоем hugo.toml.

  1. Определение языков: Ты должен указать язык по умолчанию и перечислить все поддерживаемые языки.

    # hugo.toml
    
    defaultContentLanguage = "ru" # Язык контента по умолчанию
    
    [languages]
      [languages.ru] # Код языка (ru, en, fr, de и т.д.)
        languageName = "Русский"  # Как язык будет отображаться в переключателе
        weight = 10               # Порядок в переключателе (меньше = раньше)
        contentDir = "content/ru" # Опционально: своя папка для контента этого языка
        # title = "Мой сайт на Hugo" # Можно переопределить .Site.Title для этого языка
        # baseURL = "https://example.com/ru/" # Можно свой baseURL, если языки на поддиректориях/поддоменах
        [languages.ru.params]     # Параметры, специфичные для русского языка
          copyright = "© 2025 Иван Петров"
          some_ru_specific_string = "Привет, мир!"
    
      [languages.en]
        languageName = "English"
        weight = 20
        contentDir = "content/en"
        # title = "My Hugo Site"
        # baseURL = "https://example.com/en/" # или "https://en.example.com/"
        [languages.en.params]
          copyright = "© 2025 John Doe"
          some_en_specific_string = "Hello, world!"
    
    • defaultContentLanguage: Очень важный параметр. Если Hugo находит контент без явного указания языка, он будет считать, что этот контент на языке по умолчанию.
    • [languages.<code>]: Секция для каждого языка. <code> – это стандартный код языка (например, en, ru, de-CH).
      • languageName: Человекочитаемое название языка.
      • weight: Для сортировки в переключателе языков.
      • contentDir: (Опционально) Позволяет хранить контент для каждого языка в своей подпапке внутри content/ (например, content/en/posts/, content/ru/posts/). Это самый чистый способ организации. Если не указано, Hugo будет искать файлы типа mypost.en.md и mypost.ru.md в общих папках.
      • baseURL, title, [languages.<code>.params]: Позволяют переопределить глобальные настройки сайта для конкретного языка.

Организация контента для мультиязычности:

Есть два основных способа:

  1. По имени файла (Filename-based): Файлы для разных языковых версий одной страницы лежат в одной папке, но имеют суффикс языка перед расширением.

    • content/posts/my-article.md (для defaultContentLanguage, если он ru) или content/posts/my-article.ru.md
    • content/posts/my-article.en.md
    • content/about.ru.md
    • content/about.en.md
  2. По директории (Directory-based): (Рекомендуется для большинства случаев) Ты указываешь contentDir для каждого языка в hugo.toml, как в примере выше. Тогда структура будет такой:

    • content/ru/posts/my-article.md
    • content/en/posts/my-article.md
    • content/ru/about.md
    • content/en/about.md

    Этот способ обычно нагляднее и проще в управлении.

Связывание переводов: Hugo автоматически пытается связать переведенные версии одной и той же страницы. Если у тебя файлы content/ru/posts/my-article.md и content/en/posts/my-article.md, Hugo поймет, что это переводы друг друга. Если имена файлов или пути сильно отличаются, ты можешь явно указать связь через параметр translationKey в Front Matter каждой страницы-перевода:

# content/ru/posts/статья-про-котиков.md
---
title: "Статья про котиков"
translationKey: "cat-article-123"
lang: "ru"
---
...
# content/en/blog/article-about-felines.md
---
title: "Article about Felines"
translationKey: "cat-article-123" # Тот же ключ
lang: "en"
---
...

Явное указание lang в Front Matter тоже хорошая практика, хотя Hugo часто определяет его из пути.

Шаблоны для мультиязычного сайта:

  1. Доступные переменные:

    • .Site.IsMultiLingual: true, если на сайте настроено больше одного языка.
    • .Site.Languages: Коллекция всех определенных языков (объекты из [languages.<code>] в конфиге).
    • .Site.Language.Lang: Код текущего языка (например, “ru”).
    • .Site.Language.LanguageName: Имя текущего языка.
    • .Site.Language.Params.имя_параметра: Доступ к параметрам, специфичным для текущего языка.
    • .Page.Lang: Язык текущей страницы.
    • .Page.IsTranslated: true, если для текущей страницы существуют переводы.
    • .Page.Translations: Список переведенных версий текущей страницы (не включая саму текущую страницу).
    • .Page.AllTranslations: Список, включающий текущую страницу и все ее переводы.
  2. Пример переключателя языков (Language Switcher): Обычно его делают как partial (layouts/partials/lang-switcher.html):

    {{ if .Site.IsMultiLingual }}
      <nav class="language-switcher">
        <ul>
          {{ range .Page.AllTranslations }}
            <li>
              <a href="{{ .Permalink }}" lang="{{ .Lang }}" {{ if eq .Lang $.Page.Lang }}class="active" aria-current="page"{{ end }}>
                {{ .Language.LanguageName }}
              </a>
            </li>
          {{ end }}
        </ul>
      </nav>
    {{ end }}
    

    $.Page.Lang используется для доступа к языку “внешней” страницы, так как внутри range точка . ссылается на элемент перевода.

  3. Локализация строк интерфейса (i18n): Для перевода строк в шаблонах (например, “Читать далее”, “Опубликовано”, “Поиск”) используется механизм i18n.

    • Создай папку i18n/ в корне проекта.
    • В ней создай файлы для каждого языка: i18n/ru.toml, i18n/en.toml (или .yaml/.json).
    • i18n/ru.toml:
      [read_more]
      other = "Читать далее"
      
      [posted_on]
      other = "Опубликовано"
      
      [posts_found]
      one = "Найдена {{.Count}} запись"
      few = "Найдено {{.Count}} записи" # Для русского языка важны формы few/many
      many = "Найдено {{.Count}} записей"
      other = "Найдено {{.Count}} записей"
      
    • i18n/en.toml:
      [read_more]
      other = "Read more"
      
      [posted_on]
      other = "Posted on"
      
      [posts_found]
      one = "{{.Count}} post found"
      other = "{{.Count}} posts found"
      
    • В шаблоне используй функцию i18n (или ее алиас T):
      <a href="{{ .Permalink }}">{{ T "read_more" }}</a>
      <p>{{ T "posted_on" }} {{ .Date.Format "02.01.2006" }}</p>
      <p>{{ T "posts_found" .Params.articleCount }}</p> {{/* Передача параметра .Count */}}
      

    Hugo автоматически выберет нужный перевод в зависимости от языка текущей страницы. Формы для плюрализации (one, few, many, other) очень важны для языков со сложными правилами множественного числа, как русский.

Структура URL: Hugo гибок в настройке URL для разных языков.

  • По умолчанию, если defaultContentLanguageInSubdir не установлен или false, то контент на языке по умолчанию будет в корне сайта (например, example.com/my-post/), а другие языки в подпапках (example.com/en/my-post/).
  • Если defaultContentLanguageInSubdir = true в hugo.toml, то даже язык по умолчанию будет в своей подпапке (example.com/ru/my-post/).
  • Можно настроить разные домены или поддомены для каждого языка, указав baseURL для каждого языка в [languages.<code>] секции.

Мультиязычность в Hugo – это большая, но хорошо продуманная тема. Если тебе это нужно, то Hugo предоставляет все инструменты, чтобы сделать это правильно и без лишней головной боли. Главное – аккуратно настроить конфиг и правильно организовать контент и переводы строк.

Ну как, не слишком загрузил? У нас по плану остался еще один пункт в “Расширенных возможностях” – обработка изображений. Это тоже весьма полезная штука.

Обработка изображений (ресайз, кроп, вот это всё)

Раз уж мы заговорили про ассеты, нельзя обойти стороной одну из самых ресурсоемких частей любого сайта – картинки. Hugo и тут не подкачал, предоставив мощные инструменты для обработки изображений прямо во время сборки. Забудь про ручное ресайз картинок в Photoshop для каждой превьюшки – Hugo сделает это за тебя.

Что это и зачем нужно? Hugo умеет на лету (при сборке сайта, конечно) изменять размеры изображений, обрезать их, конвертировать в другие форматы (привет, WebP!) и даже применять фильтры.

Польза очевидна:

  1. Производительность: Ты можешь генерировать картинки ровно того размера, который нужен для конкретного места на сайте (превью, полноразмерное изображение, версия для мобильных). Меньше вес картинок – быстрее загрузка сайта, счастливее пользователи и поисковики.
  2. Автоматизация: Не надо вручную готовить 100500 вариантов каждой картинки. Написал один раз логику в шаблоне – и забыл.
  3. Арт-дирекшн: Полный контроль над тем, как изображения кадрируются или вписываются в дизайн.
  4. Современные форматы: Легко генерировать версии в WebP, которые обычно легче JPEG при том же качестве, и использовать их с фолбэком для старых браузеров.

Как это работает: Ресурсы изображений

Чтобы Hugo мог обработать изображение, оно должно быть “ресурсом”. Картинки могут быть двух типов ресурсов:

  1. Ресурсы страницы (Page Resources): Это изображения, которые хранятся вместе с контентом страницы. Обычно это делается с помощью так называемых Page Bundles. Если у тебя есть пост content/posts/my-awesome-post/index.md, то все картинки, лежащие в папке content/posts/my-awesome-post/ (например, image1.jpg, hero.png), становятся ресурсами этой страницы.

    • Доступ в шаблоне страницы:
      {{ $image := .Resources.GetMatch "image1.jpg" }}
      {{/* или перебрать все изображения */}}
      {{ range .Resources.ByType "image" }}
          {{/* . - текущий ресурс изображения */}}
      {{ end }}
      
  2. Глобальные ресурсы (Global Resources): Это изображения, которые лежат в папке assets/ (например, assets/images/logo.png). К ним можно получить доступ из любого шаблона через resources.Get:

    {{ $logo := resources.Get "images/logo.png" }}
    

Получив такой объект-ресурс изображения ($image, $logo), ты можешь применять к нему различные методы обработки.

Основные методы обработки изображений:

Все методы применяются к объекту изображения и возвращают новый объект обработанного изображения.

  • .Resize "ШИРИНАxВЫСОТА [ОПЦИИ]": Изменяет размер.

    • {{ $thumb := $image.Resize "300x" }} (ширина 300px, высота пропорционально)
    • {{ $thumb := $image.Resize "x200" }} (высота 200px, ширина пропорционально)
    • {{ $thumb := $image.Resize "300x200" }} (точно 300x200, пропорции могут поехать, если исходные не совпадают)
    • Опции:
      • qNN: Качество для JPEG/WebP (например, q75 – 75%). По умолчанию обычно 75.
      • FILTER: Алгоритм ресемплинга (например, Lanczos (по умолчанию, хорошо для фото), CatmullRom, NearestNeighbor (для пиксель-арта), Box, Linear).
      • hint: Для WebP (photo, picture, drawing).
  • .Fit "ШИРИНАxВЫСОТА [ТОЧКА_ПРИВЯЗКИ] [ОПЦИИ]": Масштабирует изображение так, чтобы оно полностью поместилось в указанные размеры, сохраняя пропорции. Если пропорции не совпадают, лишнее пространство будет пустым (если фон прозрачный) или закрашено (для JPEG). Чаще используется для того, чтобы вписать картинку без искажений, а затем, если нужно, обрезать до точных размеров с помощью .Fill или если сам .Fit подгоняет размер и потом обрезает “умно”. Hugo документация говорит “scales down the image using Resize and then crops the result from the center”.

    • {{ $fitted := $image.Fit "300x200" }}
    • {{ $fittedTopLeft := $image.Fit "300x200 TopLeft q90" }} (точка привязки для кадрирования, если оно происходит: Center, TopLeft, BottomRight и т.д.)
  • .Fill "ШИРИНАxВЫСОТА [ТОЧКА_ПРИВЯЗКИ] [ОПЦИИ]": Масштабирует изображение так, чтобы оно заполнило указанные размеры, сохраняя пропорции, а затем обрезает лишнее. Идеально для создания превьюшек фиксированного размера.

    • {{ $filled := $image.Fill "300x200" }} (обрежет от центра)
    • {{ $filledSmart := $image.Fill "300x200 Smart" }} (Smart Cropping, если доступно, пытается найти интересный объект)
  • Конвертация формата (.Process "ФОРМАТ [ОПЦИИ_ФОРМАТА]") Можно сконвертировать изображение в другой формат.

    • {{ $webp := $image.Process "webp" }}
    • {{ $jpgHighQ := $image.Process "jpeg q95" }}
    • Можно объединять с другими методами:
      {{ $thumbWebp := $image.Resize "300x" | .Process "webp q80" }}
      

    Поддерживаемые выходные форматы: jpeg, png, webp, gif.

  • Фильтры (.Filter ФИЛЬТР1 [ФИЛЬТР2 ...]): Hugo позволяет применять различные графические фильтры.

    {{ $sepiaImage := $image.Filter (images.Sepia 80) }} {{/* Сепия 80% */}}
    {{ $blurredImage := $image.Filter (images.GaussianBlur 5) }} {{/* Размытие по Гауссу с радиусом 5 */}}
    {{ $grayscale := $image.Filter images.Grayscale }}
    

    Для использования этих фильтров (images.Sepia, images.GaussianBlur и т.д.) убедись, что пакет images доступен (в современных версиях Hugo он обычно доступен глобально).

Использование обработанных изображений в шаблонах: Результат любой операции обработки – это новый объект изображения со своими свойствами:

  • .RelPermalink / .Permalink: Ссылка на сгенерированный файл изображения.
  • .Width: Ширина полученного изображения.
  • .Height: Высота полученного изображения.
  • .MediaType: MIME-тип.

Пример вставки:

{{ $original := .Resources.GetMatch "photo.jpg" }}
{{ if $original }}
    {{ $thumb := $original.Resize "400x q85" }}
    <img src="{{ $thumb.RelPermalink }}" width="{{ $thumb.Width }}" height="{{ $thumb.Height }}" alt="Мое обработанное фото">
{{ end }}

Создание адаптивных изображений (Responsive Images) с srcset или <picture>: Это одна из главных причин использовать обработку изображений. Ты можешь сгенерировать несколько вариантов картинки для разных размеров экрана.

Пример с srcset:

{{ $original := .Resources.GetMatch "hero.jpg" }}
{{ if $original }}
    {{ $small := $original.Resize "600x" }}
    {{ $medium := $original.Resize "900x" }}
    {{ $large := $original.Resize "1200x" }}
    {{ $smallWebp := $small.Process "webp" }}
    {{ $mediumWebp := $medium.Process "webp" }}
    {{ $largeWebp := $large.Process "webp" }}

    <picture>
        <source srcset="{{ $smallWebp.RelPermalink }} 600w, {{ $mediumWebp.RelPermalink }} 900w, {{ $largeWebp.RelPermalink }} 1200w" type="image/webp" sizes="(max-width: 700px) 90vw, (max-width: 1000px) 60vw, 1200px">
        <source srcset="{{ $small.RelPermalink }} 600w, {{ $medium.RelPermalink }} 900w, {{ $large.RelPermalink }} 1200w" type="{{ $original.MediaType }}" sizes="(max-width: 700px) 90vw, (max-width: 1000px) 60vw, 1200px">
        <img src="{{ $medium.RelPermalink }}" width="{{ $medium.Width }}" height="{{ $medium.Height }}" alt="Адаптивный герой" loading="lazy">
    </picture>
{{ end }}

Здесь мы создаем три размера (small, medium, large) и для каждого еще и WebP версию. Затем с помощью тега <picture> даем браузеру выбор: если он поддерживает WebP – будет грузить его, если нет – обычный JPEG/PNG. Атрибут sizes помогает браузеру понять, какую картинку выбрать в зависимости от ширины viewport и как картинка отображается.

Производительность сборки и кэширование: Обработка изображений, особенно на больших сайтах, может занимать время при первой сборке. Hugo умный и кэширует результаты обработки в папке resources/_gen/images/ (или похожей). При последующих сборках, если исходное изображение и параметры обработки не изменились, Hugo возьмет результат из кэша. Этот кэш можно безопасно удалить, если нужно принудительно перегенерировать все картинки.

Качество и форматы: Экспериментируй с параметром качества (qNN). Для WebP часто можно ставить качество ниже, чем для JPEG, при сохранении визуальной составляющей. WebP почти всегда дает лучший результат по соотношению размер/качество, чем JPEG.

Вот так, инженер. Теперь ты можешь не только писать контент и верстать шаблоны, но и командовать Hugo, как ему обращаться с графикой, чтобы твой сайт был не только красивым, но и быстрым. Это закрывает наш блок про “Расширенные возможности”.

Дальше по плану – “Сборка и деплой”. Поговорим о том, как из всего этого получить готовый сайт и выложить его в интернет. Готов к финальному рывку перед практическими советами?

7. Сборка и деплой

Отлично, инженер! Ты прошел огонь, воду и медные Hugo Pipes. Теперь у тебя есть знания, чтобы собрать из кучи Markdown-файлов, шаблонов и ассетов настоящий, работающий сайт. Осталось понять, как этот сайт, собственно, “собрать” в готовый продукт и куда его потом пристроить, чтобы мир его увидел.

Этот этап – венец творения. Из кучи исходников мы получим пачку статических HTML, CSS, JS и картинок, готовую к выкладке в интернет.

Команда hugo – собираем статику

Если команда hugo server служила тебе верой и правдой для локальной разработки, то для создания финальной версии сайта используется просто команда hugo.

  1. Основная команда: Открываешь терминал в корневой папке твоего проекта и выполняешь:

    hugo
    

    По умолчанию, Hugo соберет твой сайт и сложит все готовые файлы в папку public/ в корне проекта. Именно содержимое этой папки public/ ты и будешь выкладывать на хостинг.

  2. Важные флаги для команды hugo:

    • -D, --buildDrafts: Включить в сборку черновики (страницы с draft: true). Обычно для продакшена это не нужно! Но может пригодиться для тестовых сборок.
    • -E, --buildExpired: Включить страницы с истекшим сроком годности.
    • -F, --buildFuture: Включить страницы с будущей датой публикации.
    • --cleanDestinationDir: Перед сборкой Hugo удалит все содержимое из папки назначения (public/ по умолчанию). Полезно, чтобы там не оставались старые, уже не нужные файлы от предыдущих сборок.
    • --minify: Глобальный флаг, который попытается минифицировать HTML, CSS, JS, JSON, SVG, XML. Hugo делает это довольно неплохо. Хорошая опция для продакшена, если ты не настроил более тонкую минификацию через Hugo Pipes.
    • --environment <имя_окружения>: Явно указать окружение для сборки (например, production, staging). Помнишь, мы говорили про config/production/hugo.toml? Вот этот флаг (или переменная HUGO_ENV) говорит Hugo, какой конфиг использовать. По умолчанию команда hugo (без server) использует окружение production.
    • --destination <путь>: Указать другую папку для выходных файлов вместо public/.
    • -v, --verbose: Вывести более подробную информацию о процессе сборки. Полезно для отладки.
    • --gc или --gc --minify (--gc означает “garbage collect” и может немного оптимизировать, а в связке с --minify это популярная команда для прод сборки): Эта команда включает сборку мусора для кэшей ресурсов и минификацию. Часто используется в CI/CD.

    Пример команды для продакшен-сборки:

    hugo --gc --minify --cleanDestinationDir
    

    Эта команда почистит папку public/, соберет сайт для продакшена (используя конфиг production), минифицирует всё, что можно, и оптимизирует ресурсы.

Оптимизация для продакшена (минификация всего и вся)

Мы уже касались этого в Hugo Pipes и флагах, но давай подытожим. Чтобы твой сайт летал, его ассеты должны быть как можно меньше.

  1. CSS:

    • В Hugo Pipes используй outputStyle: "compressed" при вызове toCSS для SASS/SCSS.
    • Или пропускай через | minify.
  2. JavaScript:

    • В Hugo Pipes используй minify: true в опциях js.Build.
    • Или пропускай через | minify.
  3. HTML:

    • Флаг hugo --minify минифицирует и HTML.
    • Можно также настроить минификацию HTML в hugo.toml:
      [minify]
        disableHTML = false
        # html = # тут можно указать более тонкие настройки для HTML минификатора TDewolff
        # preserveComments = false # по умолчанию
      
      Или глобально minifyOutput = true (устаревший, лучше использовать секцию minify).
  4. Изображения:

    • Используй обработку изображений Hugo для ресайза и выбора правильного качества (qNN).
    • Конвертируй в WebP для лучшего сжатия с фолбэком на JPEG/PNG.
  5. Фингерпринтинг: Не забывай | fingerprint для CSS и JS в Hugo Pipes. Это не уменьшает размер, но помогает с кэшированием в браузере – при изменении файла изменится его имя, и браузер гарантированно скачает новую версию.

Цель – выжать максимум из каждого байта. Статический сайт должен быть легким и быстрым.

Куда деплоить (Netlify, Vercel, GitHub Pages, свой сраный VPS – вариантов масса)

Итак, у тебя есть папка public/ со статикой. Куда это добро девать? Вариантов – вагон и маленькая тележка.

  1. Платформы для статики (Netlify, Vercel, Cloudflare Pages):

    • Netlify (netlify.com): Пожалуй, самый популярный выбор для Hugo. Отличный бесплатный тариф, встроенный CI/CD (подключаешь Git-репозиторий, и сайт собирается/деплоится на каждый пуш), CDN, кастомные домены, бесплатный SSL, serverless-функции, формы, аналитика. Очень удобно.
    • Vercel (vercel.com): Очень похож на Netlify, от создателей Next.js. Тоже мощный, быстрый, с отличным бесплатным тарифом и CI/CD.
    • Cloudflare Pages (pages.cloudflare.com): Сравнительно новый игрок, но быстро набирает популярность. Интеграция с глобальной сетью Cloudflare, бесплатный тариф, CI/CD.

    Эти платформы идеальны для Hugo: они понимают статику, умеют ее быстро раздавать по всему миру и берут на себя всю головную боль с инфраструктурой.

  2. Хостинг на базе Git (GitHub Pages, GitLab Pages):

    • GitHub Pages (pages.github.com): Бесплатный хостинг прямо из твоего GitHub-репозитория. Можно настроить сборку через GitHub Actions. Немного более базовый, чем Netlify/Vercel, но для простых сайтов – самое то.
    • GitLab Pages: Аналог от GitLab.
  3. Облачные хранилища + CDN (AWS S3, Google Cloud Storage):

    • AWS S3 + CloudFront: Ты можешь настроить S3-бакет для хостинга статики и подключить CloudFront (CDN от Amazon) для быстрой раздачи. Более сложная настройка, но очень гибко и масштабируемо. Потенциально дороже, если трафик большой.
    • Аналогично с Google Cloud Storage + Google CDN.
  4. Firebase Hosting (firebase.google.com/products/hosting): Еще один отличный вариант от Google. Быстрый CDN, бесплатный SSL, кастомные домены, простой деплой через Firebase CLI.

  5. “Свой сраный VPS” (DigitalOcean, Linode, Hetzner, любой другой хостер): Если ты любишь полный контроль и не боишься командной строки Linux, то это твой выбор.

    • Покупаешь VPS.
    • Устанавливаешь веб-сервер (Nginx или Apache).
    • Настраиваешь его на раздачу статики из какой-нибудь папки (например, /var/www/mysite).
    • Копируешь содержимое твоей public/ в эту папку (например, через scp или rsync).
    • Настраиваешь DNS для своего домена.
    • Устанавливаешь SSL-сертификат (Let’s Encrypt через Certbot – бесплатно и просто).
    • Следишь за обновлениями сервера, безопасностью и т.д. Больше работы, но ты сам себе хозяин.

Что выбрать? Для большинства проектов, особенно личных или небольших корпоративных, Netlify, Vercel или Cloudflare Pages – это идеальный старт. Быстро, удобно, часто бесплатно.

CI/CD – автоматизируем рутину

CI/CD (Continuous Integration / Continuous Deployment или Delivery) – это практика автоматизации сборки, тестирования и развертывания твоего сайта (или приложения) каждый раз, когда ты вносишь изменения в код (например, делаешь git push). Для статических сайтов это просто манна небесная.

Как это работает (общая схема):

  1. Ты пишешь код, создаешь контент, коммитишь и пушишь изменения в свой Git-репозиторий (GitHub, GitLab, Bitbucket).
  2. Сервис CI/CD (Netlify, GitHub Actions, GitLab CI и т.д.) замечает этот пуш.
  3. Он автоматически запускает процесс:
    • Создает временное окружение.
    • Клонирует твой репозиторий.
    • Устанавливает нужную версию Hugo (и другие зависимости, если есть, например, для PostCSS).
    • Выполняет команду сборки Hugo (например, hugo --gc --minify).
    • (Опционально) Запускает тесты, если они у тебя есть.
    • Если все успешно, он копирует содержимое папки public/ на твой хостинг.

Платформы для CI/CD:

  • Netlify, Vercel, Cloudflare Pages: У них CI/CD встроен. Ты просто подключаешь свой Git-репозиторий, указываешь команду сборки (часто они сами определяют, что это Hugo, и предлагают команду типа hugo) и папку для публикации (public). Всё. Магия происходит автоматически. Они также часто предлагают “deploy previews” – для каждого pull request создается временная версия сайта, чтобы ты мог посмотреть изменения перед мержем.
  • GitHub Actions: Мощный и гибкий инструмент CI/CD прямо в GitHub. Ты создаешь YAML-файл с описанием воркфлоу в папке .github/workflows/ твоего репозитория. Пример простого воркфлоу для сборки Hugo и деплоя на GitHub Pages:
    # .github/workflows/hugo-deploy.yml
    name: Deploy Hugo site to Pages
    
    on:
      push:
        branches:
          - main # или master, в зависимости от твоей основной ветки
      pull_request:
    
    jobs:
      build-deploy:
        runs-on: ubuntu-latest
        steps:
          - name: Checkout
            uses: actions/checkout@v3
            with:
              submodules: true  # Fetch Hugo themes (true OR recursive)
              fetch-depth: 0    # Fetch all history for .GitInfo and .Lastmod
    
          - name: Setup Hugo
            uses: peaceiris/actions-hugo@v2
            with:
              hugo-version: 'latest' # или конкретная версия '0.125.0'
              extended: true
    
          - name: Build
            run: hugo --gc --minify # Ваша команда сборки
    
          - name: Deploy to GitHub Pages
            uses: peaceiris/actions-gh-pages@v3
            if: github.ref == 'refs/heads/main' # Деплоить только если пуш в main
            with:
              github_token: ${{ secrets.GITHUB_TOKEN }}
              publish_dir: ./public
              # cname: your.custom.domain.com # Если используешь кастомный домен
    
  • GitLab CI/CD: Использует файл .gitlab-ci.yml в корне репозитория. Логика похожа на GitHub Actions.

Преимущества CI/CD:

  • Экономия времени: Не надо каждый раз вручную собирать и заливать сайт.
  • Надежность: Автоматизированный процесс уменьшает риск человеческой ошибки.
  • Быстрые итерации: Сделал изменение, запушил – через пару минут оно на сайте.
  • Спокойствие: Ты знаешь, что если сборка прошла успешно, то на сайт выкатилась рабочая версия.

Автоматизация деплоя – это то, что должен настроить каждый уважающий себя инженер. Это сильно упрощает жизнь.

Ну вот, мы и дошли до конца основного технического материала. Теперь ты знаешь, как установить Hugo, как создавать контент и шаблоны, как использовать его продвинутые фичи, и как, наконец, собрать и выложить свой шедевр в мир.

Остался последний блок – “Лучшие практики и подводные камни”. Это уже больше про опыт и советы, чтобы не наступать на грабли. Как, есть еще силенки?

8. Лучшие практики и подводные камни

Раз ты еще тут и готов слушать, значит, ты действительно настроен серьезно. Это последний рывок – поговорим о том, как не наломать дров и сделать твой путь с Hugo более гладким. Это уже не столько про фичи, сколько про здравый смысл и опыт, который приходит с набитыми шишками.

Считай это советами от старого ворчуна, который видел всякое.

Организация проекта (чтобы потом самому не охренеть)

Hugo дает тебе свободу, но свобода без дисциплины превращается в хаос.

  1. Структура – твой друг:

    • Имена файлов и папок: Придерживайся единого стиля. Обычно это kebab-case (слова-через-дефис) для файлов и папок. Латиница, естественно.
    • content/: Логично группируй контент по секциям. Не вали всё в одну кучу.
    • layouts/: Используй подпапки для разных типов шаблонов (_default, posts, partials, shortcodes). Если partials много, делай и в partials/ подпапки (widgets, seo, navigation).
    • assets/: Тоже структурируй (scss, js, images).
    • hugo.toml (или config/): Держи главный конфиг в чистоте. Комментируй не очевидные настройки. Если конфиг разрастается, подумай о переходе на директорию config/ с разбивкой по файлам (params.toml, menus.toml и т.д.).
  2. Git – с самого начала: Даже для маленького личного сайта. Инициализируй репозиторий сразу. Делай осмысленные коммиты. Это спасет тебе кучу нервов, когда что-то пойдет не так или ты захочешь откатиться.

  3. Тема vs. Локальные layouts/:

    • Если берешь готовую тему, старайся не править ее файлы напрямую. Лучше переопределяй нужные шаблоны, копируя их из папки темы в свою корневую папку layouts/ с сохранением структуры. Так ты сможешь обновлять тему, не теряя свои кастомизации.
    • Если делаешь сайт с нуля, то все твои шаблоны будут в корневых layouts/ и assets/.
  4. Документируй сложное: Если ты написал хитрый шорткод или сложный partial, оставь комментарии для себя будущего (или для коллег). Что он делает, какие параметры принимает.

  5. Архетипы – наше всё: Для каждого типа контента создай свой архетип в archetypes/. Это обеспечит единообразие Front Matter и сэкономит время.

Производительность (как не сделать тормознутый сайт на статике)

Статика по определению быстрая, но и ее можно засрать.

  1. Картинки, картинки, картинки!

    • Оптимизируй: Используй встроенную обработку Hugo для ресайза, сжатия (адекватное qNN).
    • Адаптивность: srcset и <picture> – твои лучшие друзья. Не гоняй десктопные картинки на мобилки.
    • WebP: Используй с фолбэком на JPEG/PNG.
    • Lazy Loading: Для картинок и iframe, которые не видны сразу – loading="lazy".
  2. Минифицируй всё: CSS, JS, HTML. Hugo Pipes и флаг --minify тебе в помощь.

  3. Критический CSS (по возможности): Это сложнее, но для супер-скорости можно встраивать стили для первого экрана прямо в <head>. Hugo сам по себе это из коробки не делает так просто, но через partials и readFile (или resources.Get.Content) можно что-то придумать для небольших кусков CSS.

  4. Не увлекайся сложной логикой в шаблонах на больших сайтах: Если у тебя 10 000+ страниц, частые переборы .Site.Pages без фильтрации (where) могут замедлить сборку. Используй секции, таксономии, first, after для пагинации.

  5. partialCached – твой друг (иногда): Для сложных и часто повторяющихся partials, которые не сильно зависят от контекста, используй кэширование. Но без фанатизма, замеряй время сборки.

  6. JavaScript – по минимуму: Статический сайт тем и хорош, что не требует тонны JS. Если можешь обойтись без JS или сделать что-то на чистом CSS – делай. Если JS нужен – пусть он будет легким и асинхронным/отложенным (async/defer).

  7. Хостинг/CDN: Даже самый быстрый сайт будет тормозить на медленном хостинге. Выбирай проверенные решения с CDN.

Отладка (когда всё пошло… не так)

Рано или поздно что-то сломается. Это нормально. Главное – знать, где искать.

  1. Читай ошибки Hugo: Когда сборка падает, Hugo обычно пишет, что ему не нравится. Эти сообщения часто очень информативны. Читай их внимательно, там обычно указан файл и строка.

  2. hugo server -D --verbose или hugo -v: Более подробный вывод процесса сборки может натолкнуть на мысль.

  3. Отладка шаблонов – твой главный инструмент printf и jsonify:

    • Хочешь посмотреть, что у тебя в текущем контексте . или в какой-то переменной $myVar?
      {{ printf "%#v" . }} <pre>{{ . | jsonify (dict "indent" "  ") }}</pre> ```
      Это бесценно для понимания, какие данные тебе доступны.
      
    • {{ errorf "ОШИБКА: Мое значение Title = %s" .Title }}: Искусственно вызываешь ошибку сборки, чтобы остановить процесс и посмотреть значения или проверить условие.
    • Комментируй куски шаблона ({{/* ... */}}), чтобы локализовать проблему.
  4. Проверяй public/: Иногда в браузере черт-те что, а почему – неясно. Посмотри, какой HTML Hugo реально сгенерировал в папке public/. Может, там классы не те, или ссылки битые.

  5. Front Matter и конфиг: Опечатки в YAML/TOML – частая причина проблем. Проверяй синтаксис, отступы (в YAML), кавычки.

  6. Контекст “точки” (.): Самая частая головная боль новичков. Всегда спрашивай себя: “А что такое . в этом конкретном месте шаблона?”. Особенно внутри range или в partials.

  7. Hugo Discourse Форум (discourse.gohugo.io): Если совсем зашел в тупик – иди туда. Сообщество Hugo очень отзывчивое, помогут. Только сначала сам попытайся разобраться и четко сформулируй вопрос.

Типичные ошибки новичков (и не очень)

  1. baseURL: Забыли указать или указали неправильно для продакшена. Результат – битые ссылки, неработающие CSS/JS.
  2. static/ vs assets/: Путают, куда что класть. Помни: assets/ – для того, что обрабатывается Hugo Pipes, static/ – для всего остального, что копируется “как есть”.
  3. Порядок поиска шаблонов: Hugo очень строг. Если твой шаблон не подхватывается, проверь его имя и расположение согласно “Lookup Order”.
  4. Контекст .: См. выше. Понимание этого приходит с опытом.
  5. Синтаксис шаблонов: Незакрытые {{ end }}, опечатки в именах функций или переменных.
  6. ref/relref: Не используют для внутренних ссылок, а потом при изменении структуры всё ломается. Используй их всегда!
  7. extended версия Hugo: Забывают установить, а потом удивляются, почему SASS/SCSS не компилируется.
  8. Пути к ресурсам в resources.Get: Должны быть относительно папки assets/ (или content/ для Page Resources).
  9. Излишнее усложнение: Пытаются сразу сделать всё и вся, используя самые навороченные фичи. Начинай с простого, наращивай сложность постепенно.
  10. Непонимание, что Hugo Pipes генерирует файлы с хэшем: Пытаются напрямую сослаться на assets/scss/main.scss в HTML вместо {{ $style.RelPermalink }}.

Это, конечно, не все грабли, но самые популярные. Главное – не бояться экспериментировать, читать документацию и учиться на ошибках (желательно, на чужих, подсмотренных на форуме).

Ну вот, инженер. Мы с тобой прошли большой путь. Надеюсь, этот гайддал тебе хорошую базу и понимание, что такое Hugo и как с ним работать. Инструмент мощный, гибкий, и если ты с ним подружишься, он сослужит тебе хорошую службу.

Осталось только одно – практика. Бери и делай. Удачи!