Зачем?

Предыдущая версия сайта работала на Hugo — отличном генераторе статических сайтов. Но со временем накопилось несколько проблем:

  1. PHP-зависимость для OAuth и контактов. Hugo генерирует статический HTML, а для динамических фич (авторизация через Google/Yandex, форма контактов) требовался отдельный PHP-бэкенд. Два рантайма — двойная сложность.

  2. Ручная пересборка при каждом изменении. Написал статью — нужно пересобрать сайт и задеплоить. Это отбивает желание писать.

  3. Дублирование кода. PHP-обработка шорткодов и Hugo-шаблоны жили в разных мирах, но по сути делали одно дело.

Захотелось чего-то более цельного. Чтобы один бинарник содержал в себе всё: и блог, и авторизацию, и отдачу статики.

Почему Rust?

Я пишу на Rust в свободное время, и мне нравится его подход: zero-cost абстракции, безопасность памяти без сборщика мусора, отличная экосистема для веба (Axum, Tera, pulldown-cmark).

Rust-сервер стартует за миллисекунды, ест минимум памяти и держит весь контент в памяти — без БД, без кешей, без внешних сервисов.

Что получилось?

Один бинарник (~25 MB) + директории = весь сайт:

  • content/ — Markdown-файлы с TOML-фронтматтером
  • templates/ — Tera-шаблоны (аналог Hugo layouts)
  • static/ — статика (CSS, JS, шрифты, SVG)
  • sass/ — SCSS (компилируется при старте через grass)
  • i18n/ — переводы (поддержка plural-форм)
  • config.toml — конфигурация
  • .env — OAuth-ключи

Никакого PHP. Никакой пересборки. Изменил .md файл в --dev режиме — сайт мгновенно обновился через hot reload.

Ключевые фичи

  • Динамический рендеринг. Сервер читает Markdown при старте и отдаёт HTML на лету. Никакой статической генерации — страницы формируются в момент запроса.

  • Два языка из коробки. Русский и английский. default_language в конфиге управляет URL-префиксами: / для основного, /en/ для остальных.

  • Шорткоды с разделением. Два префикса: для страниц (требует явного разрешения во фронтматтере), {sct:...} для шаблонов (всегда активен). OAuth-кнопки в header и контакты в about реализованы как короткие вызовы с параметрами и языковой поддержкой.

  • OAuth без PHP. Google и Yandex авторизация прямо из Rust-сервера. Ключи берутся из .env файла. CSRF-защита через constant-time сравнение (subtle::ct_eq).

  • RSS и Sitemap. Три режима sitemap: индексный, per-language, единый с hreflang. RSS 2.0 с <![CDATA[...]]>. ETag на SHA-256 для условных запросов (304 Not Modified).

  • Время генерации в футере. Каждая страница показывает время рендеринга: микросекунды если меньше 1 ms, миллисекунды с сотыми если больше.

  • Полное скрытие hidden-страниц. Страницы с hidden = "true" исключаются отовсюду: списки разделов, RSS, sitemap, теги, прямой доступ (404). Даже ассеты (картинки) скрытых разделов блокируются.

  • Hot reload. В --dev режиме watcher отслеживает изменения в content/ и templates/ — сайт обновляется автоматически.

  • 0 warnings. cargo clippy -- -D warnings проходит без единого предупреждения.

  • Phusion Passenger. Проект совместим с Passenger 6 (Generic Language Support). Passengerfile.json с max_instances: 1 (приложение stateful). Secure cookie через X-Forwarded-Proto — корректная работа за reverse proxy.

Технический стек

КомпонентТехнология
HTTP-серверAxum 0.7 + Tokio
ШаблоныTera 1.20
Markdown → HTMLpulldown-cmark 0.11
SCSS → CSSgrass 0.13
КонфигурацияTOML (serde)
i18nСобственная система с plural-формами
OAuthGoogle + Yandex (reqwest + HMAC-SHA256 JWT)
База данныхSQLite (sqlx) — только счётчик просмотров
Тесты24 unit + integration тестов
Docker3 compose-файла (dev/prod/runtime) + zigbuild (glibc 2.17)
Деплойmake dist → build.zip + Phusion Passenger

Всё уместилось в ~9500 строк Rust-кода (35 файлов).

Результат

Сайт работает быстро, собирается без ошибок, не требует внешних зависимостей и легко разворачивается. Писать статьи стало проще: сохранил Markdown — и изменения сразу на сайте.

Бинарник кросс-компилируется под glibc 2.17 через cargo zigbuild — работает на CentOS 7+, Debian 8+ и любом современном Linux. Деплой — один build.zip (13 MB) или Docker-образ.

Это был интересный опыт. Рекомендую всем, кто думает переписать свой пет-проект на Rust — оно того стоит.