Почему это стоит прочитать #

С прошлого разбора прошёл ровно месяц: 0.163.3 вышел 18 июня, 0.164.0 — 6 июля. После «квартала инфраструктурного взросления» с AVIF и хардненингом этот релиз выглядит скромнее: одна минорная версия, никаких громких фич.

Но два пункта в нём заслуживают внимания:

  1. Стили Chroma получили dark/light-пары. Подсветка кода наконец умеет жить в двух темах штатно, а не через самодельные костыли.
  2. Починена регрессия производительности, тянувшаяся с 0.128.0. Это не новая фича, а возврат утраченного — но на больших сайтах разница заметна.

Остальное — приятные мелочи в шаблонных функциях и один депрекейт. Breaking changes как таковых нет, обновление спокойное.

Главное за один взгляд #

Фичи:

Депрекейт:

  • resources.PostProcess объявлен устаревшим в пользу templates.Defer (issue #15086).

Мелочи, которые могут задеть:

  • Поиск имени шаблона стал регистронезависимым (#15057).
  • .Render теперь возвращает ошибку, если шаблон не найден (#15052) — раньше молча отдавал пустоту.
  • IsSet включает ключ в предупреждение о неподдерживаемом типе.

Dark/light в Chroma 🌗 #

Главная фича релиза — и, пожалуй, самая недооценённая.

Раньше стиль подсветки в Hugo был один на весь сайт: в hugo.toml задавался markup.highlight.style, и всё. Если сайт умеет переключаться между светлой и тёмной темой, приходилось выкручиваться: генерировать два CSS-файла через hugo gen chromastyles, руками оборачивать их в медиа-запросы или классы, следить за синхронизацией.

Теперь Chroma внутри Hugo знает про парные режимы, а у hugo gen chromastyles появились флаги для генерации нужного варианта. Смысл простой: светлая и тёмная схемы подсветки — это не два несвязанных стиля, а пара, и Hugo теперь так к ним и относится.

Что это значит для нашего блога. Прямое попадание. Переключатель дня и ночи здесь работает на JS с сохранением выбора в localStorage — то есть тема меняется без перезагрузки, и подсветка кода должна меняться вместе с ней. Сейчас в конфиге один [markup.highlight] на оба режима, и в тёмной теме блоки кода выглядят компромиссно. Это следующий пункт в списке доработок: сгенерировать пару стилей и подцепить их к тому же переключателю. В этот заход не делаю — обновление и так вышло насыщенным, — но фича переехала из «знать» в «внедрять».

Регрессия производительности: возврат утраченного ⚡ #

Незаметная, но приятная часть. В 0.128.0 (это июль 2024-го) в сборку просочилась регрессия, из-за которой большие сайты собирались медленнее. Симптом обсуждали на Discourse больше года, и вот в 0.164.0 его закрыли.

Ремарка из release notes честная: эффект заметен в первую очередь на крупных сайтах. Наш блог — около сотни страниц, и на таком объёме разница в пределах шума. Но сама история поучительна: регрессия прожила полтора года и полтора десятка релизов, потому что на маленьких сайтах её просто не видно.

Новые функции: crypto.Hash и hex 🔧 #

Три новые шаблонные функции:

{{ crypto.Hash "sha256" "текст" }}
{{ encoding.HexEncode "привет" }}
{{ encoding.HexDecode "d0bfd180d0b8d0b2d0b5d182" }}

crypto.Hash принимает алгоритм явным параметром — удобнее, чем существующие md5/sha1/sha256 по отдельности. Практический сценарий — стабильные идентификаторы: захешировать путь или содержимое и получить детерминированный ключ для кэша, якоря или имени файла.

Hex-функции закрывают давнюю мелочь: base64 в шаблонах был, а hex приходилось изображать вручную.

Для нашего блога — пока в копилку. Ни одного места, где это было бы нужно прямо сейчас, нет.

Депрекейт resources.PostProcess ⚠️ #

Единственное, за чем стоит следить.

resources.PostProcess использовался для отложенной обработки ресурсов — классический сценарий — PurgeCSS/Tailwind, когда нужно узнать финальный HTML, чтобы вычистить неиспользуемые классы. Теперь рекомендуется templates.Defer, механизм более общий и предсказуемый.

Нас не задевает: проверил layouts/ и themes/resources.PostProcess не используется нигде. CSS через Hugo Pipes с Tailwind мы не собираем, а Pagefind живёт отдельным npm-инструментом и к ресурсной цепочке отношения не имеет.

Если у вас Tailwind в Pipes — это первое, что нужно проверить перед обновлением: депрекейт пока не ломает, но направление задано.

Осторожно: .Render теперь возвращает ошибку #

Мелочь из списка изменений, которая может неприятно удивить. Раньше .Render с несуществующим шаблоном молча отдавал пустую строку — и сайт собирался, просто без куска контента. Теперь он возвращает ошибку, то есть сборка упадёт.

Формально это исправление, а не breaking change: молчаливая пустота — плохое поведение. Но если у вас в шаблонах есть вызов .Render с опечаткой в имени, который «работал» годами, — обновление его вскроет. Заодно поиск имени шаблона стал регистронезависимым, так что часть таких опечаток теперь, наоборот, начнёт находиться.

Инфраструктура: заодно поменяли базу образа 🐳 #

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

Блог собирается в собственном Docker-образе, куда запечены Hugo Extended, Node и Pagefind. Пошёл поднимать версию Hugo — и обнаружил, что база образа node:20-bookworm-slim достигла EOL 30 апреля 2026. То есть почти три месяца образ жил без обновлений безопасности базового слоя.

Node здесь нужен ровно для одного — установить Pagefind. Поэтому смена мажора малорискованная, и база переехала сразу на Node 24 (LTS, поддержка до апреля 2028). Итоговый образ:

hugo v0.164.0+extended linux/amd64
node v24.18.0
pagefind 1.5.2

Мораль простая и не новая: следите не только за приложением, но и за базовым слоем. Версию Hugo видно в каждом релизном посте, а FROM node:20 тихо стареет в первой строке Dockerfile, пока про него не вспомнишь.

Отдельная заметка на будущее — образ до сих пор собирался руками, поэтому и отстал. Правильный путь: собирать его в CI при изменении Dockerfile. У сборки на нашем раннере есть нюанс — он живёт в LXC-контейнере, где docker build не может применить AppArmor к промежуточным RUN-контейнерам и падает. Обходится это kaniko: он собирает образ в userspace, без демона Docker и вложенных контейнеров. Тот же приём уже используется для других наших образов.

Что это значит для нашего блога #

  • Chroma dark/light — самая полезная фича релиза именно для нас, ложится на существующий переключатель темы. В работу.
  • Фикс производительности — приятно, но на сотне страниц незаметно.
  • Депрекейт PostProcess — не задевает, проверено.
  • .Render с ошибкой — не задевает, но стоит держать в голове.
  • Обновление базы образа с Node 20 на Node 24 — по факту важнее самого апдейта Hugo.

Чек-лист обновления ✅ #

  1. Обновление спокойное. С 0.163.x на 0.164.0 идти можно без подготовки: breaking changes нет, депрекейт один.

  2. Если используете resources.PostProcess (обычно это Tailwind/PurgeCSS в Pipes) — запланируйте переход на templates.Defer:

    grep -rn 'resources.PostProcess' layouts/ themes/
  3. Если в шаблонах есть .Render — проверьте имена шаблонов: теперь несуществующий шаблон роняет сборку, а не молчит.

  4. Если у сайта есть тёмная тема — посмотрите на парные стили Chroma, это заметное упрощение по сравнению с самодельными решениями.

  5. Проверьте базовый слой сборочного образа, а не только версию Hugo. Node 20 в EOL с 30 апреля 2026:

    grep -n '^FROM' Docker/Dockerfile
  6. Стандартный прогон и сверка предупреждений до и после:

    hugo --minify --gc

Итог 🧠 #

0.164.0 — релиз-передышка после насыщенного квартала. Одна по-настоящему полезная фича (парные стили Chroma), одно давно ожидавшееся исправление (регрессия скорости из 0.128.0), горсть новых шаблонных функций и один аккуратный депрекейт.

Обновляться стоит: рисков практически нет, а dark/light-подсветка снимает целый класс костылей для сайтов с переключателем темы.

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

Источники #