Пятнадцать лет назад выбор системы управления контентом был выбором одной программы: движок хранил статьи в базе, сам рисовал страницы по шаблонам и сам же показывал редактору административную панель. Сегодня контент живёт не только на сайте: его читают приложения, письма, виджеты и голосовые интерфейсы, а витрина требует собственного фреймворка и собственной скорости. Ответом на разрыв стал composable подход: хранение контента, логика доставки и внешний вид собираются из независимых частей и общаются через API. Разберём, как WordPress совмещает монолит с открытым интерфейсом, что дают headless системы Strapi и Sanity, чем команда платит за свободу сборки и когда монолит остаётся разумным выбором.
Монолит WordPress с собственной темой и REST API в ядре движка
WordPress появился в 2003 году как движок личного блога на PHP и MySQL и вырос в самую распространённую систему управления контентом: к октябрю 2026 года на нём работает 39.6% всех сайтов мира, а среди сайтов, где система распознаётся, его доля достигает 58.3%. Цифры объясняются простотой входа: хостинг, пять минут установки, тема из каталога и готовый сайт.
Архитектура при этом остаётся классическим монолитом. Страницу рендерит PHP прямо на сервере, записи и страницы лежат в одной таблице wp_posts, произвольные поля - в wp_postmeta, темы состоят из шаблонов и хуков, плагины встраиваются в те же хуки. Всё живёт в одном коде, работает с одной базой и в одном процессе, поэтому расширить можно почти всё, но почти всё связано со всем.
Поворотным стал REST API. Инфраструктура вошла в ядро в версии 4.4, а в декабре 2016 года вышла версия 4.7 с релизным именем Vaughan и контентными эндпоинтами: записи, комментарии, термины таксономий, пользователи, метаданные и настройки стали доступны по отдельным адресам. Движок, построенный вокруг собственного рендера, впервые официально начал отдавать контент наружу в JSON.
Запрос к списку записей с выбором конкретных полей:
GET /wp-json/wp/v2/posts?per_page=5&_fields=id,title,date,link
Ответ приходит в JSON, и витриной может стать что угодно: от мобильного приложения до статического генератора. Плагин WPGraphQL добавляет поверх ядра GraphQL-схему, и WordPress превращается в полноценное headless хранилище с готовой админкой: контент без обязательного рендера, сайт без сайта.
GraphQL-запрос собирает заголовки записей одной рубрики вместе с именами авторов за единственный проход:
{
posts(first: 5, where: { categoryName: "release" }) {
nodes {
title
date
author { node { name } }
}
}
}
REST экономен и понятен с первого взгляда, GraphQL сокращает число обращений и даёт витрине ровно те поля, что нужны экрану. Внутри монолита оба протокола читают одну и ту же базу, поэтому выбор между ними ничего не меняет в хранении.
В конце 2018 года ядро получило блочный редактор, и статья перестала быть сплошным текстом: контент собирается из блоков. Хранилище при этом осталось прежним, весь набор блоков лежит одним полем в таблице записей, размеченным тегами HTML и комментариями-разделителями. Структура существует внутри редактора, но на уровне базы не читается, и это половинчатый шаг, который наглядно объясняет, почему развитие пошло в сторону отдельных хранилищ.
Headless подход и Strapi с открытым кодом на Node.js
Strapi представляет другой полюс: это open-source headless CMS, у которой витрины не было изначально. Система написана на Node.js, ставится одной командой и разворачивает панель управления с базой данных:
npx create-strapi-app@latest my-project
Модель контента описывается схемой в json-файле, и панель строится из неё автоматически: поля превращаются в форму редактора, роли и права - в настройки доступа.
{
"kind": "collectionType",
"displayName": "Article",
"attributes": {
"title": { "type": "string", "required": true },
"slug": { "type": "uid", "targetField": "title" },
"body": { "type": "richtext" },
"cover": { "type": "media", "allowedTypes": ["images"] }
}
}
Сохранение схемы открывает готовые REST и GraphQL эндпоинты, код для них вручную не пишется. Запрос с фильтром и подтягиванием связанного медиа:
GET /api/articles?filters[slug][$eq]=hello-world&populate=cover
Проект живёт активной веткой пятой мажорной версии: за октябрь 2026 года вышли релизы от v5.57.0 до v5.59.0, последний датируется 21 октября. Еженедельный ритм исправлений означает, что экосистема не заморожена и ошибки не висят в списках задач годами.
Структурированный контент Sanity и язык запросов GROQ
Sanity смотрит на контент ещё жёстче: не как на страницы, а как на документы в Content Lake, real-time базе структурированного контента. Редакторы работают в Sanity Studio, настраиваемом интерфейсе, который собирается как приложение на React и меняется вместе с задачами редакции, а разработчики запрашивают данные собственным языком GROQ.
Схема документа описывает сразу и форму редактора, и структуру ответов:
{
name: "article",
title: "Статья",
type: "document",
fields: [
{ name: "title", type: "string" },
{ name: "slug", type: "slug" },
{ name: "body", type: "array", of: [{ type: "block" }] },
{ name: "author", type: "reference", to: [{ type: "author" }] }
]
}
Поле reference создаёт связь между документами, которую GROQ потом проходит стрелкой, массив с типом block подключает Portable Text, а строковое поле превращается в простое поле ввода Studio. Одна схема обслуживает и редактора, и разработчика, и это прямое следствие принципа "контент как данные": форма описания не хранится отдельно от смысла.
Расшифровка звучит как Graph-Relational Object Queries, и формулировка создателей передаёт идею: описать ровно ту информацию, которая нужна приложению, при необходимости соединяя несколько наборов документов и собирая ответ из конкретных полей. Типичный запрос:
*[_type == "article" && defined(publishedAt)] | order(publishedAt desc) {
title,
"slug": slug.current,
"authorName": author->name
}[0...9]
В одной строке собраны фильтр по типу документа и наличию даты публикации, сортировка по убыванию, проекция трёх полей, один из которых - переименованный текущий слаг, а другой - имя из связанного документа автора через стрелку author->name. В SQL то же самое потребовало бы JOIN и псевдонимов таблиц.
Текст в Sanity хранится блоками Portable Text, где абзац и заголовок различаются полем style, а выделение задаётся массивом marks, а не тегами HTML:
{
"_type": "block",
"style": "h2",
"children": [
{ "_type": "span", "text": "Заголовок раздела", "marks": [] }
],
"markDefs": []
}
Такой текст - данные, а не разметка: приложение отрисует блок ссылкой, сайт - тегом h2, письмо - собственными стилями, и исходный документ при этом останется прежним.
Сборка витрины на Next.js и границы статической генерации
Когда хранилище перестало рендерить страницы, место витрины заняли фреймворки: Next.js для React, Nuxt для Vue, Astro для быстрой статики. Типовая сборка строится на статической генерации: страницы собираются заранее и раздаются с CDN, а свежий контент подтягивается по расписанию.
export async function getStaticProps() {
const res = await fetch(`${process.env.CMS_URL}/api/articles`);
const posts = await res.json();
return { props: { posts }, revalidate: 60 };
}
Параметр revalidate со значением 60 разрешает пересобрать страницу не чаще раза в минуту: правки доходят быстро, а нагрузка на API ограничена. В свежих версиях Next.js в App Router тот же результат даёт кеширование fetch с полем next.revalidate, а публикацию ускоряет webhook из CMS, который дергает пересборку в момент нажатия кнопки.
Вызов пересборки принимает отдельный обработчик, который CMS дергает при публикации:
export default async function handler(req, res) {
if (req.query.secret !== process.env.WEBHOOK_SECRET) {
return res.status(401).end();
}
await res.revalidate('/articles');
return res.status(200).end();
}
Обработчик сверяет секрет, просит каркас пересобрать конкретный адрес и молчит до следующей публикации. Дальше включается раздача: готовые страницы лежат на CDN-узлах рядом с читателем и отдаются за десятки миллисекунд, тогда как PHP-монолит отвечает от собственного сервера, и его скорость упирается в хостинг, кеш плагинов и базу. Статическая витрина выигрывает и на уровне архитектуры, и на уровне скорости первой отрисовки.
Граница подхода - предпросмотр. Редактору нужно видеть правку до публикации, значит витрина обязана показывать черновик по временной ссылке, а это отдельный маршрут с проверкой токена. Монолит даёт такую кнопку из коробки, composable стек собирает её руками.
Composable стек и принципы MACH для архитектуры контента
Методологию отрасли описывает аббревиатура MACH: Microservices, API-first, Cloud-native, Headless. Отраслевой альянс, основанный в 2020 году, закрепил её как общий язык описания современной сборки, и идея проста: каждая способность системы - отдельный сервис, обмен только через открытые интерфейсы, работа в облаке, хранилище контента без привязки к витрине.
Composable - ступень над headless: не одна CMS, а сборка независимых компонентов. Поиск, персонализация, коммерция, аналитика подключаются каждый своим API, и витрина становится тонким слоем сборки. Выгоды прикладные: релиз витрины не трогает хранилище, нагрузка сервисов масштабируется порознь, падение поиска не роняет статьи, а замена одного компонента не требует пересборки всего стека.
Пример прикладной сборки для медиа: редакция пишет в Sanity, витрина собирается на Next.js, полнотекстовый поиск работает отдельным сервисом со своими индексами, изображения отдаются через сервис трансформации, который режет один исходник под размеры экранов. Каждый слой заменяется отдельно, и выпуск витрины не требует доступа к хранилищу.
Цена свободы от монолита для команды и бюджета проекта
Свобода стоит денег, и счёт выставляется той же команде. В монолите новый тип контента - это плагин за вечер; в composable стеке - модель, интерфейс редактора, запрос, верстка, кеширование и миграция старых записей. Мелкая правка "добавить поле к событию" касается уже четырёх мест вместо одного.
Точек отказа становится больше: у каждой системы своя версия, свои токены, свои лимиты запросов, и деплои нужно синхронизировать. Безопасность тоже распадается: права, ключи API и списки CORS настраиваются в каждой части стека, а утечка одного ключа открывает ровно тот сервис, к которому он подходит.
Миграция тоже ложится на команду. Записи выгружаются из старой системы постранично через REST, поля приводятся к новой схеме, вложения переносятся в новое хранилище медиа, а редиректы со старых адресов прописываются вручную. Перенос честнее разбивать на этапы: новый раздел сначала запускается на composable витрине, старые страницы остаются на монолите, и система переезжает кусками без остановки работы сайта.
Монолит держится, когда сайт один, контент простой, дизайн собирается из готовой темы, а в штате нет разработчика. Сайт на WordPress поднимается за вечер, обновляется кнопкой и переживает смену подрядчиков. Composable окупается там, где каналов несколько, дизайн нестандартный, а релизы витрины идут каждую неделю.
Критерии выбора между монолитом и композицией на живых задачах
Выбор архитектуры сводится к честным ответам на шесть вопросов:
- сколько каналов одновременно потребляет один и тот же контент;
- есть ли в команде frontend разработчик на постоянной поддержке;
- нужен ли дизайн, который не собирается из готовой темы;
- как часто выходят правки и как быстро они должны появляться на витрине;
- какие требования предъявляются к нагрузке и скорости отрисовки страниц;
- кто отвечает за обновления и безопасность каждого компонента.
Один канал и простые материалы отвечают на первые два вопроса в пользу монолита, и спорить здесь не о чем. Несколько каналов, приложение вдобавок к сайту и нестандартный интерфейс склоняют выбор к композиции. Гибрид остаётся легитимным ответом: WordPress как админка и репозиторий при витрине на Next.js - проверенный путь, когда редакторам привычна одна панель, а продукту нужны скорость и свобода.
Эволюция не отменила монолит: 39.6% сайтов мира не дадут этого сделать. Она добавила выбор там, где раньше выбора не было: хранить контент там, где он не привязан к чужой теме, и менять витрины поколениями, не трогая само содержание. Композиция - не мода и не догма, а расчёт, который окупается ровно тогда, когда число витрин и скорость релизов переходят порог, за которым монолит начинает тормозить.