Почему это стоит прочитать #
С прошлого разбора (v0.160.1, 8 апреля) накопилось пять релизов: 0.161.0, 0.161.1, 0.162.0, 0.163.0 и патчи до 0.163.3 (18 июня). Это не один минор, а целый квартал развития — поэтому разбор догоняющий.
Если сжать до двух заголовков, их два:
- AVIF — Hugo научился кодировать и декодировать AVIF прямо в image pipeline. Третий формат рядом с JPEG и WebP, и для фото-блога это самое интересное.
- Хардненинг безопасности — Node-инструменты теперь запускаются с
--permission,text/html-контент запрещён по умолчанию, ужесточеныsecurity.http.urls, плюс транзитом приехали XSS-фиксы из Go 1.26.3.
Остальное — приятные улучшения конфигурации (slice-based permalinks, гибкие идентификаторы в именах файлов, вложенные vars в css.Build) и порция депрекейтов. Breaking changes есть, но точечные — разберём, кого они задевают.
Главное за один взгляд #
v0.161.0 / 0.161.1 (28–29 апреля) — фичи + первый хардненинг:
- Node-инструменты (PostCSS, Babel, TailwindCSS) запускаются с флагом
--permission. Нужен Node ≥ 22, а Tailwind теперь только как npm-пакет (standalone-бинарь больше не поддерживается). - Ужесточены дефолты
security.http.urls; добавленsecurity.node.permissions(включаяAllowChildProcess). - Slice-based permalinks — конфигурация permalinks с
target-матчерами (как вcascade). - Гибкая схема идентификаторов в именах файлов:
_language_en_,_role_,_version_. - Вложенные vars в
css.Build/css.Sass:@import "hugo:vars/dark".
v0.162.0 / 0.162.1 (26–28 мая) — AVIF + security:
- 🎯 Поддержка AVIF (encoder + decoder) в image pipeline.
text/html-контент запрещён по умолчанию — новая политикаsecurity.allowContent.- Go 1.26.3: фиксы XSS в
html/template(CVE-2026-39826, CVE-2026-39823). - Проверка
security.http.urlsна каждом редирект-хопеresources.GetRemote; отказ от symlink-записей вresources.Get.
v0.163.0 (8 июня) — тюнинг AVIF + депрекейты:
- AVIF по умолчанию
quality = 60(≈ JPEG/WebP q75), content-aware chroma subsampling черезhint, качество настраивается per-format. - Депрекейт глобального
imaging.qualityиImaging.Compression(переезжают в per-format конфиги). - Добавлен
.IsBranch, депрекейт.IsNode; депрекейтbaseUrlв jsconfig.
v0.163.1 / 0.163.2 / 0.163.3 (11–18 июня) — патчи:
- Security: bump
golang.org/x/image, нормализация integer-IPv4 вhttp.urls, отказ от symlink вos.ReadDir/ReadFile/Stat/FileExists. - Escape
langв дефолтном рендере код-блоков; сохранение non-ASCII пробела после summary-разделителя; поддержка вариантов конфигов babel/postcss; фикс регрессии коллизии имён page/section.
Дальше — что это значит на практике.
AVIF: третий формат в pipeline 🖼️ #
Главная фича квартала. В 0.162.0 Hugo получил полноценную обработку AVIF — и кодер, и декодер (issue #7837, висел с 2020 года). AVIF при том же визуальном качестве заметно легче JPEG и обычно легче WebP, плюс умеет HDR (у Hugo есть демо-сайт, который имеет смысл смотреть на HDR-экране вроде Retina).
В 0.163.0 фичу довели до ума:
- Дефолтный
qualityдля AVIF опустили до 60 — оказалось, JPEG/WebP с q75 сопоставимы с AVIF q60. Не гоните качество вверх по привычке. - Появился
hint(как у WEBP): дляlossyфото по умолчанию используется YUV420 chroma subsampling, а для текста/иконок/схем — 444. Это резко снижает память на кодирование больших картинок. - Качество можно задавать на формат:
[imaging]
# глобальный quality теперь deprecated
[imaging.webp]
quality = 80
[imaging.avif]
quality = 60
hint = "photo"В шаблоне AVIF получается так же, как WebP — через .Process / метод ресайза:
{{ $img := resources.Get "img/cover.jpg" }}
{{ $avif := $img.Process "resize 1600x avif" }}
<picture>
<source srcset="{{ $avif.RelPermalink }}" type="image/avif">
<source srcset="{{ ($img.Process "resize 1600x webp").RelPermalink }}" type="image/webp">
<img src="{{ ($img.Resize "1600x").RelPermalink }}" alt="…">
</picture>Что это значит для нашего блога. Раздел /photos уже генерит 800/1600/2400 px в JPEG + WebP и отдаёт через <picture>/srcset. Добавить AVIF — это правка шаблона, который собирает <source>-ы (плюс per-format quality в hugo.toml): первым <source type="image/avif">, дальше WebP, в конце JPEG-фолбэк. Браузеры берут первый поддерживаемый. На фотографиях с retina это даст ощутимую экономию трафика при том же качестве. В этот заход не делаю — но это очевидный следующий шаг для фото-ленты.
Хардненинг безопасности 🔒 #
Самая «breaking» часть. Изменения копились через все релизы:
Node-инструменты под --permission (0.161.0). PostCSS, Babel и TailwindCSS теперь запускаются с Node permission-флагом и правами из security.node.permissions. Следствия:
- нужен Node ≥ 22;
css.TailwindCSSтребует Tailwind CLI как npm-пакет — standalone-бинарь больше не годится.
Если вы собираете CSS через Hugo Pipes с Tailwind/PostCSS — это первое, что стоит проверить перед обновлением.
Запрет text/html-контента по умолчанию (0.162.0). Новая политика security.allowContent гейтит, какие media-типы допустимы для страниц в /content. text/html теперь запрещён по умолчанию. Сайты, где есть руками написанные .html-файлы в content/ (или их эмитит content adapter), вернут доступ явно:
[security]
allowContent = ['.*']Прочее (0.162.0 → 0.163.3):
security.http.urlsперепроверяется на каждом редирект-хопе вresources.GetRemote— мимо валидации больше не проскочить через 30x.- Symlink-записи отклоняются в
resources.Get, а в 0.163.1 — ещё и вos.ReadDir/ReadFile/Stat/FileExists. - Go подняли до 1.26.3 с фиксами XSS в
html/template(CVE-2026-39826, CVE-2026-39823) — это касается любого сайта, рендерящего недоверенный ввод в шаблонах.
Любопытная ремарка от мейнтейнера в release notes 0.163.1: всплеск security-репортов в последнее время — это не «Hugo стал менее безопасным», а работа новых AI-инструментов, которые берут security-модель Hugo за baseline и методично её простукивают.
Slice-based permalinks 🔗 #
В 0.161.0 конфигурация permalinks стала гибче (старая map-форма продолжает работать). Теперь это список с теми же target-матчерами, что и в cascade:
permalinks:
- target:
kind: page
path: "/books/**"
pattern: /books/:year/:slug/
- target:
kind: page
pattern: /other/:slug/Удобно, когда для разных секций нужны разные схемы URL без дублирования настроек. Нашему блогу с плоской структурой posts/ это пока избыточно, но знать про механизм полезно — особенно если когда-нибудь заведём разделы со своими правилами URL.
Гибкие идентификаторы в именах файлов 🏷️ #
Тоже из 0.161.0. Раньше язык кодировался как mypost.en.md. Теперь доступна расширяемая схема префиксов в имени файла:
| Префикс | Что задаёт | Где применимо |
|---|---|---|
_language_ | Язык | Контент и layout-файлы |
_role_ | Роль | Контент и layout-файлы |
_version_ | Версия | Контент и layout-файлы |
_outputformat_ | Output format | Layout-файлы |
_kind_ | Page kind | Layout-файлы |
content/mypost._language_en_.md эквивалентно content/mypost.en.md, но открывает дорогу к версиям и ролям контента. Для одноязычного блога — мимо, для документации с версиями — заметное упрощение.
css.Build: вложенные vars 🎨 #
Развитие фичи из 0.160. К @import "hugo:vars" добавились вложенные наборы — @import "hugo:vars/<name>". Классический сценарий — палитры под prefers-color-scheme:
[params.style]
primary = "#000000"
background = "#ffffff"
[params.style.dark]
primary = "#ffffff"
background = "#000000"@import "hugo:vars";
@import "hugo:vars/dark" (prefers-color-scheme: dark);Аккуратно, но не для нас: переключатель дня/ночи на блоге работает через JS и localStorage (явный выбор пользователя, без авто), а не через prefers-color-scheme. Палитра живёт в :root. Так что фича в копилку «знать», но не «внедрять».
Депрекейты и мелочи #
.IsNode→.IsBranch(0.163.0): если в шаблонах есть.IsNode, мигрируйте на.IsBranch(issue #11574).- Глобальный
imaging.quality(0.163.0): помечен deprecated в пользу per-format ([imaging.webp],[imaging.avif], …). Старая форма пока работает и даже не всегда ругается — но направление задано. - jsconfig
baseUrlубран (был deprecated). - Сохранение non-ASCII пробела после summary-разделителя (0.163.3) — мелочь, но именно для кириллических текстов с `
` это правильное поведение.
- Фикс коллизии имён page/section (0.163.3, #15046) — регрессия, всплывшая в 0.163.x; ещё одна причина брать самый свежий патч, а не 0.163.0.
Что это значит для нашего блога #
Прогнал блог на 0.163.3 — собирается чисто, 90 страниц, 0 ошибок, 52 изображения обработаны без проблем. Конкретика:
- Запрет
text/htmlнас не задел — вcontent/нет руками-сверстанных.html, всё в Markdown.security.allowContentтрогать не нужно. - Node-permission мимо — Tailwind/PostCSS через Pipes не используем, Pagefind живёт отдельным npm-инструментом.
languageCode→locale: попутно почистил deprecation вhugo.toml(languageCode = 'ru-RU'→locale = 'ru-RU'). Этот warning ушёл. Оставшиеся три (.Site.LanguageCode,.Language.LanguageCode,.Language.Direction) приходят из шаблонов темыtypo— уйдут, когда обновится тема.- AVIF — главная зацепка на будущее: добавить третий
<source>в фото-pipeline и срезать вес ленты/photos.
Чек-лист обновления ✅ #
Прыгайте сразу на 0.163.3, а не на 0.163.0 — патчи .1/.2/.3 закрывают security-дыры и регрессию коллизии имён.
Если собираете CSS через Tailwind/PostCSS/Babel в Pipes — поставьте Node ≥ 22 и Tailwind как npm-пакет; проверьте
security.node.permissions.Если в
content/есть.html-файлы — добавьтеsecurity.allowContent = ['.*'], иначе страницы выпадут из сборки.Почистите депрекейты:
# languageCode → locale в конфиге # .IsNode → .IsBranch в шаблонах grep -rn '\.IsNode' layouts/ themes/Если работаете с изображениями — присмотритесь к AVIF и per-format
quality; глобальныйimaging.qualityуводите в[imaging.<format>].Стандартный прогон и сверка warning’ов до/после:
hugo --minify --gc
Итог 🧠 #
0.161–0.163 — это квартал «инфраструктурного взросления» Hugo: новый формат изображений (AVIF), методичный security-хардненинг и более гибкая конфигурация (permalinks, идентификаторы файлов, вложенные CSS-vars). Громкая фича ровно одна — AVIF; всё остальное либо про безопасность, либо про удобство.
Breaking changes реальны, но адресные: они бьют по сайтам с Tailwind-в-Pipes и с HTML-контентом в content/. Если вы (как наш блог) пишете на чистом Markdown и не гоняете Node-сборку CSS — переход бесшовный, достаточно поднять версию и пробежаться по депрекейтам.
Если сидите на 0.160 или раньше — есть смысл прыгать сразу на 0.163.3, мимо промежуточных: так вы заодно получите все security-фиксы одним движением.
Источники #
- Релиз 0.161.0: gohugoio/hugo v0.161.0
- Релиз 0.162.0: gohugoio/hugo v0.162.0
- Релиз 0.163.0: gohugoio/hugo v0.163.0
- Релиз 0.163.3: gohugoio/hugo v0.163.3
- Конфигурация AVIF: gohugo.io/configuration/imaging/#avif
- Security-модель Hugo: gohugo.io/about/security/