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

Сценарий, знакомый любому, кто собирал магазин на headless-стеке: карточка товара тянет описание из CMS, цену и остаток — из ERP через промежуточный сервис, отзывы — из отдельной базы, фасеты и похожие товары — из поискового движка. Четыре вызова, каждый в среднем по 120 мс. Если дёргать их последовательно, до первого байта уходит почти полсекунды ещё до рендера. Если один апстрим отвечает 503, а обработки ошибки нет — пользователь видит не карточку без отзывов, а пустую пятисотку. Композитный сайт работает ровно до тех пор, пока вы держите в голове обе эти арифметики: сложение задержек и умножение вероятностей отказа.
Что именно композитного в композитном сайте
Монолитная CMS — WordPress, Битрикс, Drupal — держит у себя всё: контент, шаблоны, рендер, админку, кеш. Запрос приходит в одно приложение, оно же и отвечает. Композитный сайт разрезает эту вертикаль на независимые сервисы, каждый из которых отвечает за одну зону, и сшивает их на уровне фронтенда или шлюза.
Типичный набор слоёв:
- Источники данных. Headless CMS (Strapi 5, Directus, Contentful), товарный каталог, ERP или учётная система, поиск, платежи, отзывы, формы. У каждого своя админка и свой цикл релизов.
- Слой оркестрации. BFF* или GraphQL-шлюз, который принимает один запрос фронтенда и разворачивает его в несколько обращений к источникам. Здесь живут таймауты, ретраи, нормализация форматов и склейка ответов.
- Рендер-слой. Next.js 15, Nuxt 3.x, Astro 5, SvelteKit. Он решает, что отдавать как статику, что собирать на сервере по запросу, а что подтягивать в браузере.
- Кеш и доставка. CDN перед рендером, кеш на уровне шлюза, иногда кеш на уровне отдельного источника. Это не оптимизация «потом», а условие работоспособности схемы.
Путь одного запроса
Пользователь открывает карточку. Запрос попадает на CDN. Если страница в кеше и свежа — ответ уходит из ближайшей точки присутствия, ни один из четырёх сервисов даже не узнаёт о визите. Это нормальный, целевой путь, и в хорошо настроенном проекте так обслуживается большая часть трафика.
Промах по кешу идёт дальше: CDN стучится в рендер-слой, тот запрашивает у шлюза данные карточки, шлюз параллельно обращается к CMS, каталогу, поиску и отзывам, собирает ответ, рендер отдаёт HTML, CDN кладёт его в кеш. Разница между первым и вторым путём — от десятков миллисекунд до секунды. Вся инженерия композитного сайта сводится к тому, чтобы вторым путём ходило как можно меньше запросов и чтобы он не разваливался, когда всё-таки ходит.
Где накапливается задержка
Последовательные вызовы складываются, параллельные — берут время самого медленного. Четыре вызова по 120 мс последовательно дают 480 мс, параллельно — около 120 мс плюс накладные расходы шлюза. Поэтому первое, что смотрят в композитной сборке: нет ли случайной последовательности там, где могла быть параллельность. Классическая ловушка — цепочка «сначала получить ID товара из CMS, потом по нему спросить цену»: зависимость по данным, параллелить нечего, надо либо денормализовать, либо класть ID в путь URL.
Вторая арифметика неприятнее. Если четыре апстрима независимы и у каждого хвост распределения такой, что 1% запросов медленный, то вероятность, что хотя бы один из четырёх попадёт в этот хвост, — примерно 1 − 0,99⁴, около 4%. Доступность складывается так же: четыре сервиса по 99,9% дают около 99,6% на композицию, если отказ любого из них валит страницу. Хвосты и отказы отдельных сервисов на композиции не усредняются, а накапливаются.
В монолите медленный запрос — это один медленный запрос. В композитном сайте медленный запрос — это самый медленный из четырёх, и он встречается в четыре раза чаще, чем вы ожидали по средним.
Из этого следуют два практических требования к слою оркестрации. Таймаут на каждый апстрим — жёсткий и заметно меньше, чем таймаут всего запроса. И деградация по зонам: отзывы не пришли — рендерим карточку без блока отзывов, остаток не пришёл — показываем товар без точного количества, но цену и описание отдаём. Разделение данных на критичные и некритичные делается один раз на этапе проектирования и дальше определяет всё поведение под сбоями.
Кеш как несущая конструкция
В монолитной CMS кеш можно выключить, и сайт продолжит работать — медленно, но работать. В композитном выключение кеша обычно означает, что внешние API начинают получать трафик, на который не рассчитаны, и упираются в лимиты по запросам. Поэтому кеш здесь не ускоритель, а часть конструкции.
Работают три механизма, и важно не путать их уровни:
- Статическая генерация. Страница собирается на сборке и лежит файлом. Идеально для контента, который меняется релизами: лендинги, статьи, справка. Плохо для цен и остатков.
- Кеш на CDN с управляемым временем жизни. Директивы
Cache-Control: s-maxageиstale-while-revalidateпозволяют отдавать слегка устаревший ответ мгновенно, пока в фоне идёт обновление. Семантика этих директив описана в RFC 9111 и RFC 5861, у провайдеров к ним добавляются свои: у Fastly —Surrogate-ControlиSurrogate-Key, у Cloudflare на корпоративных тарифах —Cache-Tag. Конкретные имена заголовков и доступность тегов на вашем тарифе сверяйте в документации провайдера, они различаются. - Инвалидация по событию. Редактор нажал «опубликовать» — CMS отправляет вебхук, рендер-слой или CDN сбрасывает ровно те страницы, которые затронуты. Это то, за что в композитных стеках платят больше всего нервов.
Прогрев и стратегии инвалидации — отдельная большая тема, здесь важен только один вывод: вы обязаны знать, какой слой кеша отдал конкретный ответ. Без заголовка статуса кеша в ответе и без метрики hit ratio отладка композитного сайта превращается в гадание.
Чем это отличается от монолитной CMS
| Ось | Монолитная CMS | Композитный сайт |
|---|---|---|
| Кто рендерит страницу | Само приложение CMS | Отдельный фронтенд-слой, данные приходят по API |
| Отказ одного компонента | Чаще всего падает весь сайт | Можно деградировать по зонам, если это заложено |
| Инвалидация кеша | Внутренняя, из коробки | Собирается руками: вебхуки, теги, сброс по маршрутам |
| Превью черновиков | Работает в админке сразу | Нужен отдельный режим обхода кеша и защищённый маршрут |
| Кто меняет вёрстку | Контент-менеджер в визуальном редакторе | Разработчик через релиз, если не внедрён слой блоков |
| Стоимость владения | Хостинг плюс обновления | Подписки на SaaS-сервисы, CDN, трафик, плюс дежурство |
| Отладка медленного ответа | Один лог, один профайлер | Трассировка через 3–5 систем, нужна сквозная корреляция |
Что ломается первым
Порядок примерно такой, по частоте, а не по тяжести.
- Инвалидация. Контент опубликован, на сайте старая версия. Причин обычно две: вебхук не дошёл (сеть, подпись, упавший обработчик) или сброшен не тот маршрут. Товар появляется в карточке, но не в листинге и не в фасетах, потому что сбросили одну страницу из трёх затронутых.
- Превью и черновики. Редактор не видит изменений, пока они не на продакшене, и начинает публиковать черновики, чтобы посмотреть. Режим превью с обходом кеша — не украшение, а требование от редакции.
- Лимиты внешних API. Промах по кешу на пике трафика, бот-краулер или релиз со сбросом всего кеша разом — и SaaS-источник начинает отдавать 429. Схема без кеша на уровне шлюза в этот момент складывается целиком.
- Частичные отказы без деградации. Самый обидный класс: сервис отзывов недоступен, а не работает весь каталог, потому что ошибка одного вызова пробрасывается наверх.
- Расхождение данных между зонами. Цена в карточке из одного источника, цена в корзине — из другого, кеш у них разный. Правило простое: деньги и остатки на финальных шагах берутся из одного источника истины и не кешируются так же агрессивно, как описания.
- Форма и корзина. Всё, что завязано на сессию, из статики не отдаётся. Эти фрагменты выносятся в клиентский рендер или в отдельные некешируемые маршруты, иначе персональные данные одного пользователя попадут в кеш другого. Это не теория, а частая и очень дорогая ошибка конфигурации CDN.
Что мерить, чтобы видеть реальную картину
Чужих цифр здесь брать неоткуда: задержка композиции зависит от набора источников, регионов и профиля трафика. Снимайте свои, минимум по пяти показателям:
- Cache hit ratio на CDN отдельно по типам маршрутов. Листинги, карточки и статика ведут себя по-разному, усреднённая цифра бесполезна.
- TTFB на попадании и на промахе — две разные метрики, смешивать их нельзя.
- p95 и p99 по каждому апстриму со стороны шлюза, а не со стороны поставщика. Вас интересует время с сетью и TLS.
- Доля деградированных ответов: сколько страниц отдано без блока отзывов, без остатков, без рекомендаций. Нулевое значение означает либо идеальные апстримы, либо отсутствие метрики.
- Лаг публикации: время от нажатия «опубликовать» до появления на сайте, по 95-му процентилю. Это показатель, по которому вас оценивает редакция.
Что делать дальше
Начните с карты зависимостей: нарисуйте каждый шаблон страницы и перечислите, какие источники в него входят и какие из них критичны. Дальше — три конкретных шага. Включите сквозную трассировку с единым идентификатором запроса через шлюз и все апстримы, иначе разбор инцидента займёт часы. Задайте по каждому апстриму таймаут и поведение при его срабатывании, и проверьте это поведение искусственным отключением сервиса, а не на словах. Опишите политику кеширования для каждого типа маршрута на одной странице и держите её рядом с кодом шлюза.
Из чтения по делу: раздел о кешировании и ревалидации в документации вашего фреймворка (в Next.js 15 модель кеширования заметно менялась между мажорными версиями, читайте строго под свою) и RFC 9111 — он короче, чем кажется, и снимает половину споров о том, что делает s-maxage.
* BFF (Backend for Frontend) — промежуточный сервис, который собирает данные из нескольких источников под нужды конкретного клиента и отдаёт их одним ответом. Фронтенд при этом не знает, сколько систем за ним стоит.
Карточки для маркетплейсов: Таблица товаров и папка фотографий на входе — комплект слайдов и описаний на выходе, по требованиям Wildberries и Ozon.
Спросить за 5 ₽Источники: RFC 9111: HTTP Caching, Next.js Documentation
Частые вопросы
Композитный, headless и MACH — это одно и то же?
У меня другая версия фреймворка, модель кеширования совпадёт?
Страница не обновляется после публикации, кеш сброшен. Где искать?
Какие подводные камни чаще всего всплывают именно в проде?
Композитный сайт дешевле монолитной CMS?
Как редакция будет верстать страницы, если шаблоны живут в коде?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.