4 min de lectura

Как устроен этот блог

Этот блог — папка с markdown-файлами внутри репозитория сайта. Ни базы данных, ни админки, ни серверного рантайма: команда сборки превращает каждый файл в готовый HTML, а сервер потом просто отдаёт файлы с диска. Ниже — почему я выбрал такую схему и что она означает для того, кто открывает страницу.

Почему markdown, а не CMS

Первая причина скучная: статья — это текст, а текст хорошо живёт в git. У каждой правки есть автор, дата и причина; откатить неудачную формулировку — одна команда. База данных так не умеет: там есть только текущее состояние и надежда на бэкап.

Вторая причина в том, что файл в репозитории лежит рядом с кодом, который его показывает. Меняя вёрстку статьи, я вижу настоящую статью, а не lorem ipsum из демо-данных. Это дисциплинирует: типографику приходится проверять на живом тексте со всеми его списками, таблицами и длинными ссылками.

Что даёт такой подход на практике:

Чем я за это плачу

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

Правило, которого держусь: если страницу можно отдать файлом, её надо отдавать файлом.

Формулы считаются на сборке, а не в браузере

В тексте про инженерию иногда нужна формула. Обычный путь — подключить библиотеку рендеринга и дать ей работать на стороне читателя. Я делаю это на сборке: markdown проходит через конвейер unified, где формулы превращаются в готовую разметку KaTeX ещё до того, как файл попадёт на сервер.

Порядок шагов важнее, чем кажется:

  1. разбор markdown и извлечение фронтматтера;
  2. GFM-расширения — таблицы, сноски, зачёркивание;
  3. пометка формул как формул;
  4. перевод в HTML-дерево;
  5. санация — чистим исходное дерево, пока в нём ещё нет чужой разметки;
  6. KaTeX, подсветка кода, якоря у заголовков;
  7. сборка строки HTML.

Санация стоит именно пятым шагом, до KaTeX: если пропустить её после, она выпотрошит математическую разметку, потому что не знает её тегов. Сам конвейер выглядит так:

const html = String(
  await unified()
    .use(remarkParse)
    .use(remarkFrontmatter, ["yaml"])
    .use(remarkGfm)
    .use(remarkMath)
    .use(remarkRehype)
    .use(rehypeSanitize, schema) // до KaTeX, а не после
    .use(rehypeKatex)
    .use(rehypeStringify)
    .process(source),
);

Статьи лежат в content/blog/*.md, имя файла становится адресом страницы. Никакого поля «URL» в форме, никакой рассинхронизации между ссылкой и заголовком.

Что из этого получает читатель

Что происходит CMS с рендером в браузере Этот блог
Открытие страницы запрос к базе, шаблон, ответ готовый файл с диска
Превращение формулы в вёрстку на устройстве читателя один раз на сборке
Отключённый JavaScript пустая страница или сырой текст страница читается целиком

Последняя строка — главная. Текст, списки, таблицы, подсветка кода и формулы существуют в HTML как есть. Скрипты на страницах статей не нужны ни для чего, кроме мелочей вроде аналитики, и их отсутствие ничего не ломает.


Время чтения считается, а не выдумывается

Подпись «7 мин чтения» под заголовком — не украшение, а обещание. Считаю её честно, по числу слов NN в исходном markdown при скорости v180v \approx 180 слов в минуту1:

t=Nv,v=180t = \left\lceil \frac{N}{v} \right\rceil, \qquad v = 180

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

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

Сноски

  1. 180 слов в минуту — рабочая константа, которую я выбрал для русского текста, а не измеренная величина. Если окажется, что оценка систематически врёт, поменяю одно число в коде.

Todos los artículos