Как сделан этот сайт
На собеседованиях меня уже пару раз спрашивали, почему у бэкендера сайт вообще без клиентского фреймворка. Проще один раз описать здесь, чем пересказывать каждый раз заново.
Дальше по порядку: на чём в итоге остановился, что ещё смотрел и где потом об это споткнулся.
Стек
- 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. Пока их всего две, разницы не чувствуется, но штук на десять я упрусь в то, что из-за правки одной подписи приходится пересобирать весь проект.
И тёмная тема по умолчанию, а не по системной настройке, — это выбор в пользу того, как сайт выглядит, а не того, к чему привык посетитель. Решение спорное, знаю. Переключатель в шапке — компромисс.