Разработка сайта с использованием Static Site Generator (SSG)

опубликовано: ⏰ 9

Решил поделиться своим опытом использования генераторов статических сайтов (SSG). Для этих целей использую Hugo, с начала 2025 года. Собственно, о нём и пойдёт речь, без LLM и вайбкодинга. Плюс небольшая реклама и дань уважения разработчикам за их труд, так как Hugo распространяется бесплатно и с открытым кодом.

Введение

Опыт разработки сайтов с CMS к ним был очень давно (2006 – 2008, на PHP). И если честно, ни времени, ни желания повторять это упражнение не было. Использовать WordPress или другие CMS я не стал, причины ниже.

Почему именно Static Site Generator (SSG). Причин несколько:

  1. Производительность – на выходе генератора обычные HTML-файлы, нет никакой серверной логики и как следствие дополнительной нагрузки на хостинг. Поэтому можно брать самый простой вариант.
  2. Безопасность – отсутствие серверного кода, а значит и потенциальных уязвимостей (не учитываем возможные ошибки в конфигурации самого хоста). Да и меньше последствия от DDoS-атак. Отдаём только статику, которая ещё и кешируется.
  3. Простота наполнения – здесь спорно, конечно, но в целом SSG позволяют работать с контентом в Markdown, что для меня лично очень удобно. Всё версионирование содержимого сайта через GIT, внесение изменений через MR, возможность откатить в любой момент до нужной версии.

Варианты SSG

Когда делал первую версию сайта, релевантного опыта не было с подобными генераторами и выбирал больше по интуиции, но в целом попал в точку. Есть сайт со списком популярных SSG – A List of Static Site Generators for Jamstack Sites. Первые 4–6 генераторов из этого списка пробовал настроить и использовать под свои потребности. У каждого есть свои плюсы и минусы, некоторые дают довольно неплохой набор функционала сразу из коробки.

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

Установка и использование

Здесь вне конкуренции. Все, что нужно – это установить бинарный файл и запустить команду hugo build.

Всё, никаких NPM, долгих сборок, скачиваний зависимостей и прочего. Так же есть поддержка обновления в реальном времени, через hugo server.

Локализация

Одно из важных требований – поддержка локализации сайта. У меня сейчас используются два языка: русский, английский. Поэтому это был один из блокирующих критериев выбора. Часть SSG отсеялись сразу, так как не поддерживали локализацию.

Разные генераторы реализуют локализацию по-своему:

  1. Для одних необходимо делать два отдельных сайта под разные языки, ресурсы практически которых не пересекаются. Можно сказать, что локализация не поддерживается.
  2. У других сайт вроде бы общий, но структура каталогов статей, под разные языки, сильно изолирована друг от друга. Чуть лучше, чем первый пункт, но пользоваться неудобно.
  3. Локализация есть, но без возможности сделать статью только на одном языке. Не всегда есть время и возможность сразу сделать перевод, но выбора не оставляют.

Как сделано в Hugo? Если кратко, то просто и удобно – Multilingual mode:

  • Все переводы статей можно хранить рядом в виде:
    • /blog/article-1/assets/*.webp
    • /blog/article-1/index.en.md
    • /blog/article-1/index.fr.md
  • Делаем нужный перевод и кладём в каталог статьи, рядом с оригиналом.
  • Если какого-то языка нет, то создаём пустой файл и во frontmatter указываем headless:true.
  • Ресурсы из assets могут быть общими для всех языков.
  • Есть возможность использовать функцию i18n для перевода текста (список терминов в виде отдельных файлов).
  • Локализацию поддерживают и шаблоны.

Кастомизация и шаблоны

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

Изначально, в качестве базового шаблона использовал Ananke. Для быстрого старта это было удобно, но так как он рассчитан на определённую универсальность, то в нагрузку получил много не нужного кода. Это часто мешало при попытках что-то подправить, плюс возникал некоторый бардак с правками в шаблоне и в файлах сайта. В итоге, где-то за неделю по вечерам + выходной, сверстал текущую версию сайта под свои потребности полностью с нуля. Некоторые куски оформления взяты из шаблонов в интернете. И нет, вайбкодинг с LLM не использовал :).

В Hugo используется Go Template (html/template, text/template) для описания шаблонов. Сама структура заранее определена по шаблонам и секциям. Основные шаблоны:

  • Base – основной шаблон, в котором описывается общая структура сайта ( index.html).
  • Home – главная страница.
  • Page – шаблон для обычных страниц (страница в разделе).
  • Section – шаблон для секций (список страниц в разделе).
  • Taxonomy – шаблон для таксономий (теги, категории и т.д.)

При этом они могут переопределяться на разных уровнях. Для этих страниц, например, используется шаблон Page:

  • Статья 1 – шаблон по умолчанию, для всех страниц, на любом уровне вложенности.
  • Статья 2 – переопределённый шаблон, только для продуктов.

Параметры страниц

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

Если указан featuredTitle, то будет текстовая обложка. Если featuredImage, то будет изображение.

article.md
Markdown
---
featuredTitle: WebDev
featuredSubtitle: Site
categories: [ 'web' ]
tags: [ "web", "site", "ssg", "generator", "hugo" ]
title: "Разработка сайта с использованием Static Site Generator (SSG)"
toc: true
show_comments: true
---

# Введение

В самом шаблоне просто выбирается нужное:

article-card.html
Dockerfile
{{ $image := "" }}

{{ if .Params.featuredImage }}
    {{ $image = (resources.Get .Params.featuredImage).RelPermalink }}
{{ end }}

{{ if $image }}
    <img src="{{- $image -}}"/>
{{ end }}

{{ if and (not $image) .Params.featuredTitle }}
    <div>{{ .Params.featuredTitle }}</div>
    {{ if .Params.featuredSubtitle }}
        <div>{{ .Params.featuredSubtitle }}</div>
    {{ end }}
{{ end }}

Свои компоненты и элементы интерфейса

Если смотреть на SSG на базе NodeJS, то там обычно есть ReactJs или Vue, соответственно и возможность делать свои компоненты или подключать внешние.

С Hugo не пробовал делать компоненты на ReactJS, но вроде как это возможно через отдельный проект на NodeJS. В свою очередь, для Hugo есть альтернативный вариант компонентов – shortcodes. Напоминает Web Components – параметризованный шаблон HTML, который можно использовать прямо из Markdown.

Например, карточка контактных данных

В markdown
Dockerfile
{ { <contact-card title="Telegram" fontIcon="telegram" url="https://t.me/tekBlueprint" >}}
    Telegram group with news and discussions.
{ {</contact-card>}}
Шаблон contact-card.html
Dockerfile
{{ $link := .Get "url" }}
{{ $fontIcon := .Get "fontIcon" }}
{{ $imgIcon := .Get "imgIcon" }}
<div>
    <div>
        {{ if $fontIcon }}
            <i class="bi bi-{{$fontIcon}}"></i>
        {{ end }}
        {{ if $imgIcon }}
            <img src="{{ $imgIcon }}" style="width: 1rem;"/>
        {{ end }}
        {{ .Get "title" }}
    </div>
    <div>
        <p>
            <a href="{{ $link }}" target="_">{{ i18n "follow" }}</a>
            {{ .Inner }}
        </p>
    </div>
</div>

Есть ещё partials – это что-то вроде фрагментов, в которых доступен контекст текущей страницы. Например, они используются у меня для блоков <head/>, <footer/> страницы. Опять же с поддержкой локализации.

RSS, карта сайта, robots.txt

Если честно, не помню уже как было в других генераторах, они уже отсеялись до этого шага. По-моему, тоже всё поддерживалось.

По Hugo, из коробки есть и работает:

  • RSS:
    • Автоматически генерируется, под каждый язык.
    • Можно настроить через шаблон. Обложку для статей так и не доделал, времени нет.
    • Использую как новостную ленту.
  • Карта сайта:
    • Автоматически генерируется, под каждый язык.
    • Можно настроить через шаблон.
  • robots.txt:
    • Автоматически генерируется, но у меня вручную настроен.
    • Тоже настраивается через шаблон.

Управление ресурсами

Генераторы на базе NodeJS позволяют обрабатывать и упаковывать ресурсы, как и для обычных веб-приложений. Здесь всё достаточно понятно и просто.

В Hugo есть схожий функционал. Изображения, стили, скрипты, шрифты хранятся либо assets/ и static/. Если используются ресурсы из assets/, то их необходимо обрабатывать с помощью дополнительных функций: resources.GetMatch, resources.Get и т.д. На статику static/ можно ссылаться как есть, она копируется в исходном виде.

Так же возможно упаковывать ресурсы (CSS/JS) через пайплайны. Например,

Dockerfile
{{- $cssVendor := slice
        (resources.Get "vendor/lib1/style.css")
        (resources.Get "vendor/lib2/style.css")
        | resources.Concat "assets/vendor.css" | resources.Minify | resources.Fingerprint}}

В итоге будет получен файл assets/vendor.css с уникальным хешем в имени файла.

При необходимости, можно выполнять обработку изображений. Например, автоматическое преобразование изображения из статьи в WebP формат через Process:

Dockerfile
{{ $linkToCover = (.Process "webp").RelPermalink }}

Генерация сайта

Здесь особых требований не было, но победила скорость генерации Hugo.

Сайт пока небольшой (чуть больше 100 страниц), время сборки сайта около 300 мс. При этом генерируются не только страницы, но и RSS, sitemap, все страницы по тегам и категориям, сжатие CSS/JS ресурсов.

Shell
                  │ EN  │ RU
──────────────────┼─────┼─────
 Pages            │ 107 │ 106
 Paginator pages  │   2 │   2
 Non-page files   │  60 │   0
 Static files     │  12 │  12
 Processed images │   0 │   0
 Aliases          │  68 │  67
 Cleaned          │   0 │   0

Total in 331 ms

Ограничения

У SSG есть определённые ограничения, которые стоит учитывать. Так как не серверной части, то дополнительный функционал придётся добавлять через сторонние сервисы/скрипты. Например, для комментариев к статьям используется giscus, который работает через GitHub.

Поиск тоже нужно делать либо через сторонние сервисы, либо через JS скрипты, которые будут работать на клиенте (lunr). Это я так и не успел сделать, есть только в документации.

Заключение

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

Web Site Ssg Generator Hugo