На главную

Как сделан этот сайт

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

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

Стек

  • Astro — генератор статики. Выполняет код на сборке, отдаёт готовый HTML.
  • TypeScript — только на сборке, в браузер не попадает.
  • Обычный CSS с переменными. Без препроцессоров и утилитных классов.
  • Свой VPS под nginx — раздаёт готовую статику.
  • Ноль зависимостей в рантайме. Клиенту не уезжает ни одна библиотека.

Почему Astro

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

Что рассматривал ещё:

Next.js
Избыточен. Он решает задачи, которых у меня нет: рендеринг по запросу, загрузка данных, ревалидация кэша. Я бы платил сложностью за то, чем не пользуюсь.
HTML руками
Честный вариант, я его всерьёз рассматривал. Отказался из-за двух языков: пришлось бы держать две копии разметки и синхронизировать их при каждой правке. Это ровно тот класс задач, где ручная синхронизация рано или поздно расходится.
Hugo
Собирает быстрее и не тянет Node. Но шаблоны Go мне менее привычны, а выигрыш в скорости сборки на двух страницах равен нулю.

Цена решения: нужен Node на сборке, и раз в год придётся обновлять зависимости. Меня устраивает — обновляется одна зависимость, а не тридцать.

Почему без React

Стили и скрипты встроены прямо в HTML — отдельных файлов нет вообще. Вот что весит каждая страница на диске и что из этого уходит по сети, потому что nginx отдаёт всё сжатым:

СтраницаНа дискеПо сети
/165 КБ29 КБ
/en/148 КБ26 КБ
/stack/38 КБ10 КБ

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

Скриптов на странице два, оба написаны руками и лежат прямо в HTML: переключатель темы на семь строк и подсказки к технологиям. Вместе — меньше четырёх килобайт, и это весь JavaScript на сайте.

Интерактивному тут почти негде взяться: на странице текст, ссылки и пара схем. Тащить ради этого несколько сотен килобайт рантайма — значит платить за то, чего на странице попросту нет. Если понадобится настоящий сложный виджет, Astro позволяет подключить React точечно на один компонент, а не на весь сайт. Пока не понадобилось.

Побочный эффект, которого я не планировал, но которому рад: без JavaScript сайт теряет только подсказки и переключатель темы, а весь текст остаётся на месте. И печатается в PDF прямо из браузера.

Два языка без дублирования разметки

Тексты лежат в src/content/ru.ts и en.ts, разметка одна на оба языка. Правлю формулировку в одном месте — обе страницы пересобираются.

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

Схемы

Схемы в разделе «Архитектура» — не картинки. Каждая описана как список узлов и связей:

{ id: "kafka", col: 3, row: 4, kind: "broker" }
{ from: "progress", to: "kafka", async: true }

Компонент считает координаты и рисует SVG. Что это даёт: подписи переводятся, а геометрия остаётся общей на оба языка; схема наследует цвета темы и читается на светлом фоне и в печати; текст на схеме — настоящий текст, его находит поиск по странице и читает скринридер.

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

Доступность и темы

Тёмная тема по умолчанию, светлая по переключателю, выбор запоминается. Контраст всех пар «текст на фоне» я прогнал по WCAG AA в обеих темах. Заодно пришлось затемнить синий на залитых кнопках: белый текст на исходном оттенке давал 3,2:1 при норме 4,5.

Блоки на схемах различаются не только цветом, но и типом рамки — сплошная, штриховая, пунктирная. Иначе схема разваливается при дальтонизме и в чёрно-белой печати.

Из-за доступности подсказки к технологиям не получилось сделать на одном CSS. :hover закрывает половину пользователей: на телефоне наведения нет вообще, а с клавиатуры подсказку не открыть. Поэтому чип — это button, карточка появляется от наведения, фокуса и нажатия, закрывается по Escape и связана с чипом через aria-describedby. Ради этого я и добавил второй скрипт: семьдесят строк ради того, чтобы подсказками мог пользоваться не только человек с мышью.

По мелочи: ссылка «к содержимому» для клавиатуры, видимый фокус, prefers-reduced-motion отключает анимации, широкие схемы прокручиваются внутри себя и не тянут за собой страницу.

Сборка и выкладка

Выкладываю просто, без CI-обвязки: собираю сайт локально (npm run build), заливаю готовую статику на свой сервер по SSH, а раздаёт её nginx на домене bonkurov.ru. HTTPS — сертификат Let’s Encrypt, он же продлевается сам; http редиректит на https.

Перед сборкой всегда прогоняю свой маленький скрипт-проверку. Он ищет в текстах незаполненные заглушки вида [~450] и не даёт собрать сайт, если что-то нашёл. Причина бытовая: черновики я держу в тех же файлах, что и готовый текст, и однажды чуть не выложил страницу с [назвать домен] в первом абзаце.

Чего здесь нет

Аналитики
Ни счётчиков, ни пикселей, ни баннера про cookie. Мне не нужны данные о посетителях настолько, чтобы за них расплачивался тот, кто открыл страницу.
Внешних запросов
Кроме шрифтов — ничего. Всё остальное лежит рядом с HTML.
Блога
Раздел, который обновляется дважды в год, работает против автора: он показывает не активность, а её отсутствие.
Админки
Тексты правлю в редакторе кода. Для сайта, который меняется раз в квартал, CMS — это лишний сервис, который надо поддерживать и который однажды сломается.

Что бы сделал иначе

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

И тёмная тема по умолчанию, а не по системной настройке, — это выбор в пользу того, как сайт выглядит, а не того, к чему привык посетитель. Решение спорное, знаю. Переключатель в шапке — компромисс.