Книжная полка в Quartz
Книжная полка на сайте Smirnoff нужна не для того, чтобы спрятать BOOK-LIBRARY внутри Quartz. Её задача проще и полезнее: дать читателю понятную публичную витрину — с категориями, обложками и описаниями — прямо рядом с заметками и проектами сайта.
При этом роли не смешиваются. Quartz остаётся статическим сайтом и отвечает за показ. BOOK-LIBRARY остаётся источником истины: там живут книги, категории, обложки, админка, импорт, переводы и скачивание через backend.
Такое разделение сделано специально. Сайт не превращается в файловый склад, не берёт на себя задачи backend библиотеки и при этом может аккуратно показать, что находится на полке.
Страница content/library.md
В content/library.md почти нет тела страницы:
---
title: "Библиотека"
description: "Категории и книги из BOOK-LIBRARY..."
publish: true
---На первый взгляд это выглядит слишком пусто, но здесь пустота работает на архитектуру. Slug library перехватывается в quartz.layout.ts: для него подключается LibraryPage, а обычные заголовок, meta, TOC и Backlinks отключаются.
Markdown-файл остаётся маленькой, но важной точкой входа. Он задаёт title, description, publish-флаг и URL страницы, а сам интерфейс библиотеки собирается уже компонентами Quartz.
LibraryPage
quartz/components/LibraryPage.tsx создаёт каркас интерфейса, то есть всё, что читатель видит до загрузки конкретных данных:
- hero-блок библиотеки;
- status-сообщение загрузки;
- контейнер категорий;
- hover preview;
- modal для подробной карточки книги.
Компонент получает настройки из bookshelf.json и прокидывает их в data-атрибуты:
data-library-backend-base-url;data-library-catalog-path;data-library-preview-description-length.
Так страница остаётся статической в сборке, но получает достаточно контекста, чтобы на клиенте выбрать источник данных и корректно отрисовать полку. Дальше всё оживляет libraryPage.inline.ts.
bookshelf.json
Настройки библиотеки лежат в quartz/static/data/bookshelf.json:
{
"schemaVersion": 1,
"backendBaseUrl": "https://book.slavx.ru",
"catalogPath": "/static/generated/bookshelf-catalog.json",
"previewDescriptionLength": 260
}backendBaseUrl говорит, где искать live API BOOK-LIBRARY. catalogPath указывает на статический fallback-каталог. previewDescriptionLength ограничивает длину описания в hover preview, чтобы подсказка оставалась компактной и не превращалась в отдельную статью поверх интерфейса.
Live API
На клиенте библиотека сначала пытается загрузить свежие данные из BOOK-LIBRARY:
- категории:
/api/categories; - книги категории:
/api/categories/:id/books?limit=24&offset=0.
Книги грузятся лениво. Первая категория запрашивается сразу, чтобы страница быстро получила видимое наполнение. Остальные подтягиваются при приближении к viewport через IntersectionObserver. Это снижает количество запросов на первом экране и не заставляет посетителя ждать всю библиотеку сразу.
Static fallback
Если live API не ответил, скрипт пробует загрузить static catalog по catalogPath. Это файл quartz/static/generated/bookshelf-catalog.json.
Fallback важен не как техническая страховка «на всякий случай», а как способ не оставлять читателя перед пустой страницей:
- сайт остаётся рабочим, даже если backend временно недоступен;
- GitHub Pages продолжает отдавать статическую витрину;
- можно показать curated-снимок библиотеки без запроса к API;
- часть проблем CORS не ломает страницу полностью.
Static catalog не заменяет backend. Это запасной слой для публичной витрины: достаточно самостоятельный, чтобы страница не развалилась, но не претендующий на роль главной базы библиотеки.
Build-time generation
generateBookshelfStaticAssets находится в quartz/util/bookshelfCatalog.ts. Он вызывается из quartz.layout.ts и может во время сборки:
- сходить в
backendBaseUrl; - получить категории;
- получить книги по каждой категории;
- скачать обложки;
- записать JSON-каталог;
- сохранить mirrored covers в
quartz/static/generated/book-library-covers.
Путь к каталогу вычисляется из catalogPath. Если он начинается с /static/generated/, файл пишется внутрь generated-директории Quartz.
В практическом смысле это превращает живую библиотеку в статический снимок, который Quartz умеет отдать без обращения к backend. Для публичного сайта это удобная граница: данные подготовлены заранее, а страница всё ещё остаётся частью обычной статической сборки.
Mirrored covers
У каждой книги может быть coverUrl из BOOK-LIBRARY. Генератор пытается скачать обложку, определить расширение по URL или content-type и сохранить файл как статический asset.
После этого в catalog у книги появляется coverSrc, например:
{
"coverSrc": "/static/generated/book-library-covers/10.webp"
}На странице это даёт быстрые локальные обложки и уменьшает зависимость витрины от внешнего ответа в момент просмотра. Если картинка не загрузилась, LibraryPage показывает fallback с первой буквой названия — не так красиво, как обложка, зато карточка остаётся читаемой и не ломает ряд.
Rails, preview и modal
Интерфейс библиотеки сделан как набор горизонтальных rails. У каждой категории есть строка обложек и собственный scrollbar, поэтому полка ощущается как витрина: можно быстро пробежать глазами по разделу и остановиться на книге, которая заинтересовала.
На десктопе hover или focus показывает preview: категория, формат, название, автор и короткое описание. Это быстрый слой знакомства без лишнего клика. Если нужно больше контекста, клик открывает modal.
В модальном окне больше места, поэтому туда выводятся обложка, номер книги, формат, автор и полное описание. Escape и кнопка закрытия возвращают пользователя назад.
Почему Quartz — витрина
Quartz хорош как публичный слой:
- его легко деплоить как статику;
- он связывает библиотеку с заметками и проектами;
- он умеет показывать curated-контент;
- он не требует backend для каждой страницы.
Но Quartz не должен решать задачи BOOK-LIBRARY:
- хранение файлов книг;
- временные ссылки на скачивание;
- импорт;
- переводы;
- админские операции;
- контроль публикации.
Если смешать эти роли, сайт станет сложнее и небезопаснее. Поэтому BOOK-LIBRARY остаётся источником истины, а Quartz показывает аккуратную публичную полку. Для читателя это выглядит как единая страница библиотеки, но внутри ответственность разделена там, где ей и место.
generated — не ручной источник
quartz/static/generated нельзя воспринимать как место для ручного редактирования каталога. Это результат генерации. Если нужно поменять книгу, категорию или обложку, править нужно BOOK-LIBRARY или конфигурацию интеграции, а не generated JSON.
Для Markdown-задач особенно важно не запускать build без необходимости: он может обновить generated catalog и covers, даже если вы меняли только статьи. Иначе рядом с правкой текста легко получить шумные изменения в сгенерированных данных.
Итог
Книжная полка в Quartz — это статическая витрина с live API и fallback. Она показывает читателю категории, обложки и описания, но не забирает на себя роль библиотеки. Static catalog и mirrored covers делают эту витрину устойчивее, а fallback к BOOK-LIBRARY сохраняет связь с настоящим источником данных. Такая граница делает проект проще: Quartz отвечает за сайт и связи, BOOK-LIBRARY — за книги и данные.