Всем привет! Я Леша Кузьмин, руководитель направления Frontend в AGIMA. В этой статье мы подробно рассмотрим безголовые CMS: какие тут есть подводные камни, как быть с архитектурой проектов, интеграциями и динамическими страницами. Посмотрим на управление сайтом не только со стороны разработчиков, но и контент-менеджеров. Бонусом пройдемся по SEO-производительности и настройке серверов.
Будет полезно разработчикам с опытом в Koa, Express, Strapi и частично React. Еще статья пригодится тем, кто любит копаться в документации — я покажу примеры, которые помогут с ней разобраться.
Что такое Headless CMS и когда ее стоит использовать
Headless CMS — это гибкая система управления контентом. Из ее особенностей:
Удобный интерфейс для контент-менеджеров.
Low Code Solution. Можно быстро и просто расширять функционал без привлечения разработчиков.
Бесшовный (омниканальный) интерфейс взаимодействия, т. к. Headless CMS работают с разными видами фронтенда. Это помогает придерживаться подхода единого источника «истины» для всех потребителей API.
Мультиязычность. Можно заносить решения из коробки без проблем.
При первом приближении Headless CMS очень похожа на стандартный подход с Django, Laravel или WordPress с прикрученным JSON API. Но дело в том, что у Headless-подхода есть несколько дополнительных преимуществ. Выделю главные из них, сравнив Strapi CMS и WordPress:
Полная и простая кастомизация дизайна
Фронтенд в Strapi — это отдельное приложение. Он не имеет отношения к самой CMS, что дает большую гибкость для создания интерфейсов.Скорость отдачи контента
Спорный момент, но Google заявляет, что сайты разработанные с использованием Headless CMS работают быстрее, чем, например, на WordPress.Безопасность
У WordPress достаточно много уязвимостей, и поэтому даже простой бэкэнд на нем не совсем безопасен.Простота обслуживания и деплоя.
Со Strapi всё проще: собираем Docker-образ, пушим изменения кода в репозиторий, и, если настроен CI/CD, всё деплоится без проблем.
Мультиязычность
В Strapi мультиязычность подключается проще, чем в WordPress. А также поддерживается интеграция со сторонними сервисами для переводов: можно отделить зону ответственности и не пускать переводчиков в систему управления контентом, и в таком случае, они будут использовать свой привычный инструмент.
Примеры использования Headless CMS
Давайте рассмотрим для примера наш проект для сети фитнес-клубов World Class.
Мы оценили ТЗ, стек, прикинули инструменты, ресурсы и начали строить архитектуру.
Для работы над сайтом заказчика из всех Headless CMS мы выбрали Strapi, хотя довольно часто используем и Payload. Основное преимущество — возможность работы с ними в режиме Standalone, что есть далеко не у всех Headless CMS.
Пробуем SSG
В первой итерации мы выбрали подход SSG, то есть генерацию страниц на стороне сервера. Вот какая картина у нас получилась:
Рассмотрим подробнее блок со схемой связей между сервисами:
У нас есть Headless CMS, здесь — это Strapi. У Strapi есть хуки жизненного цикла, которые позволяют выполнять какие-либо действия, например при изменении данных. В нашей концепции Strapi Hooks дергают отдельный CI/CD, который запускает пересборку приложения, и пушат новую сборку SSG-приложения на хостинг. В промежутке есть UI Kit с компонентами и их пропсами. Компоненты мы потребляем в SSG через UI Kit, а пропсы потребляем как схемы в Strapi.
Какие могут быть сложности с SSG
Первое — это зависимость приложения от внесенных в Strapi изменений, а соответственно, пересборка на каждое изменение в системе контента. Да, можно было бы локализовать проблему, покрыв хуками только ту часть данных, которую мы будем менять, но SSG не позволяет пропускать сборки при изменении контента.
Почему мы вообще смотрели в сторону SSG? Всё просто, мы не хотели отдельное серверное приложения для фронтенда. Это привело бы к дополнительным нагрузкам на инфраструктуру. Нам же нужно было добавлять новые страницы из админки, в чем и кроется основная проблема. Она же может привести к зацикленности пересборки при частых изменениях.
Но назвать этот подход полностью нерабочим нельзя. Наоборот, если выяснится, что в контенте не будет частых изменений, то он будет оправдан, просто с некоторыми ограничениями.
Второй фактор — отказоустойчивость. Да, в современном мире это звучит странно — можно ведь просто обернуть приложение в Docker и отдать его девопсам, которые настроят необходимый уровень автоматизации. Но здесь чуть подробнее.
Мы планировали разделить CI/CD на несколько стадий, и помимо основной роли для сборки приложения было выделено еще две. Одна из них занималась экспортом JSON для описания компонентов и позволяла достаточно строго соблюдать контракты, а вторая — для пересборки роутинга, когда у нас нет необходимости пересобирать всё приложение.
Здесь и кроется самая большая проблема. Сервисы связаны между собой и чаще всего они просто не смогут существовать друг без друга.
Переходим на SSR
Мы подготовили прототип предыдущего решения с SSG, после чего убедились в своих опасениях наглядно.
Нужно было что-то менять. Так как требования к стеку были ограничены заказчиком, мы решили поменять подход для рендеринга страниц и перейти на SSR — рендеринг на стороне сервера.
При этом подходе проще работать с динамическими адресами, что также упрощает и схемы данных: из них пропали прямые адреса страниц для сборки роутинга, а на замену появились просто названия или идентификаторы страниц.
Вот какие преимущества SSR в сравнении с SSG можно выделить:
Динамический рендеринг страниц.
Да, но теперь мы потребляем ресурсы сервера.Отсутствие сервиса сборки роутинга.
Теперь роутинг динамический и собирается он самостоятельно.Повышение отказоустойчивости.
Теперь у нас два сервиса вместо трех. Они крутятся в Kubernetes, могут общаться между собой через сетевой слой и скорость их взаимодействия достаточно высокая. Это помогает избегать длительного ожидания при сборке контентных страниц.Нет очередей для сборки.
Мы избавились от очередей пересборки, ведь теперь после обновления контента он тут же попадает прямо на сайт.
Взаимодействие со стороны разработчика
Теперь посмотрим, как это всё работает в интерфейсе. Вот наш план работы с CMS:
создаем компонент или группу компонентов в Strapi;
создаем схему для страницы;
определяем динамические зоны и добавляем в них доступные компоненты, которые сможет выбирать контент менеджер;
добавляем контент;
разбираем запрос на получение данных.
В админке мы увидим вполне базовый интерфейс с Content-Type Builder. В нем есть отдельный блок с компонентами — нажимаем «создать новый компонент».
Вводим имя компонента и группу. Если группы не существует, мы можем ее создать на лету. Это удобно, потому что так мы систематизируем расположение компонентов в дереве.
Дальше добавляем необходимые поля в рамках схем и в рамках самих компонентов. Здесь мы можем добавлять в том числе и динамические компоненты.
Добавляем в слайдер компонент слайда. Тут же выбираем стратегию, как мы его будем использовать — как повторяющийся компонент или как отображаемый однократно.
Собираем базовую схему и сохраняем ее, чтобы появилась коллекция для наших страниц.
В коллекции страниц мы тоже указываем имя, а Strapi автоматически предлагает, как будет называться наш API-эндпоинт. Его можно переименовать, если вдруг это понадобится.
Дальше необходимо добавить динамические зоны. Это делается так же просто, как и добавление полей, ведь мы взаимодействуем с одним интерфейсом.
Для создания динамической зоны необходимо написать ее имя.
Система предложит создать или выбрать из созданных компонентов те, что мы будем использовать в этой конкретной динамической зоне.
У нас получилась вот такая схема:
Здесь мы видим секцию вопросов-ответов — FAQ и слайдер. Они находятся в динамической зоне component.
А что там у контент-менеджера?
Теперь посмотрим, что делает в CMS контент-менеджер. Он переходит в свой раздел Content Manager, который никак не относится к Content-Type Builder.
Переходим в коллекцию, создаем новую запись и заполняем необходимые поля. Внизу появляется развернутое окошко с динамической зоной. Мы видим, что сейчас там отображается динамический компонент FAQ и слайдер.
Мы можем выбрать любой из них. Давайте выберем слайдер и в рамках этого компонента добавим слайды. Под каждым слайдом есть отдельная кнопка Add an entry, которая позволяет добавлять новые айтемы.
Здесь мы можем перетаскивать и менять компоненты местами в рамках одного компонента или по всей динамической зоне. Порядок компонентов поменяется, страница пересоберется, и получится динамический контент. С точки зрения работы с CMS, это весьма удобно, т. к. у контент-менеджера появляется доступ к изменению интерфейса.
Компоненты добавили, данные тоже. А как нам их получить?
Мы добавили данные и создали компоненты. Теперь нужно получить данные компонентов и их порядок, чтобы отрендерить на фронтенде.
Идем в настройки, в роли, в нашем случае — это Public. Открываем доступ к API, сообщаем, что у нас есть варианты поиска find и findOne.
Сохраняем, идем в Postman и пишем следующий запрос, чтобы получить от API все данные, которые мы добавили:
http://localhost:1337/api/pages/1?populate[0]=components&populate[1]=components.FAQItem&populate[2]=components.BeautifulSlide&populate[3]=components.BeautifulSlide.image
Разберем запрос по частям:
А вот какой ответ мы получим от Strapi:
Здесь мы видим массив с объектами: это ID, его порядок и название компонента с дополнительными внутренними компонентами — слайдами. В блоке FAQ Strapi также автоматически сгенерировал имя компонента, с которым мы будем работать в нашем фронтенд-приложении.
На мой взгляд, это не самый удобный способ работы с компонентами. И связано это со сложностью формирования самого запроса, так как в приведенном примере мы получаем только пару компонентов в динамической зоне — представьте, если их будет несколько десятков. Поэтому предлагаю рассмотреть другой.
Меняем стратегию работы с компонентами
Для этого нужно разбить компоненты на схемы:
Создаем схемы для базовых компонентов.
Создаем схему для лейаута страницы.
Создаем схему для связи компонентов и страниц.
Добавляем контент.
Разбираем запросы на получение данных.
Здесь мы тоже создаем дополнительные схемы для вопросов и ответов и для секций отзывов, в которые накидываем базовые поля. Но самое интересное — это базовые компоненты и их порядок вместе с лейаутами. Остановимся на них подробнее чуть позже.
Здесь мы тоже можем добавлять слайды, только теперь задаем их не в рамках динамической зоны, а создаем каждый раз новую запись. Один слайд может отображается как на одной, так и на разных страницах, что дает определенную гибкость. Всё это настраивается связями в рамках схем данных.
Для получения этих данных идем в настройки и разрешаем доступ ко всем вновь созданным схемам, чтобы мы могли обращаться к ним по API.
Что изменилось в получении данных?
До того как мы посмотрим на запросы, давайте сначала разберем общую схему взаимодействия сервисов:
Наше SSR- или SSG-приложение обращается к таблице с порядком компонентов и, зная имя лейаута, запрашивает список компонентов, которые необходимо отрендерить. Имея этот список, мы получаем данные из системы, запрашиваем у UI Kit их внешний вид и после этого собираем страницу.
Как выглядят запросы:
список лейаутов — http://localhost:1337/api/layouts;
порядок компонентов — http://localhost:1337/api/components-orders.
Расширим запрос для получения имен компонентов. Мы хотим получить список, поэтому запрашиваем имена базовых компонентов:
http://localhost:1337/api/components-orders?populate[0]=base_components
Теперь отфильтруем запрос по имени страницы. Включаем дополнительный флаг filter, сообщаем, что у нас есть таблица layout, а у нее — поле slug. Он должен быть равен index, чтобы наш список в итоге отобразился:
http://localhost:1337/api/components-orders?populate[0]=base_components&filters[layouts][slug][$eq]=index
Этот список не интереснее, чем в предыдущем примере. Нам нужно его трансформировать примерно так:
В целом здесь достаточно только имени и ID, чтобы понять, какой компонент подставить и получить данные.
Для примера запросим их для отзывов:
http://localhost:1337/api/my-review-items?populate[0]=image
Отфильтруем по ID из связи:
http://localhost:1337/api/my-review-items?populate[0]=image&filters[my_review][id][$eq]=1
Так мы получим все данные для отзывов.
Мы обращаемся непосредственно к items, которые являются review. Дальше сообщаем, что мы хотим добавить туда изображение, ведь увы вложение медиаконтента никак не предусмотрено. Дальше накладываем фильтры и сообщаем, что review идентификатора должен быть равен единице. На этот ответ Strapi возвращает JSON, в котором будут все необходимые данные.
А что там с SEO?
Мы сравнили показатели нового сайта World Class со старым сайтом, и вот что у нас получилось:
Самые интересные для нас показатели — это Performance, который вырос на 20%, и скорость отрисовки первого контента. Да, это синтетические тесты, и возможно, показатель х10 не совсем релевантный, но сайт и правда сейчас открывается быстро — проверьте по ссылке выше сами.
Как это работает на фронтенде. Примеры на NuxtJS и NextJS
В этих примерах я опущу момент получения данных на стороне приложения. Мы рассмотрим только примеры для сборки бандлов страниц.
Вот как происходит динамическая сборка в NuxtJS:
Здесь мы получаем список компонентов в динамических зонах, которые перебираются, и с использованием именованных слотов добавляют необходимые для нас компоненты в шаблон. В итоге всего несколько строк кода позволяют в динамике собирать страницу со всеми данными, которые прилетели от контент-менеджера из Strapi.
С React чуть сложнее. Он требует создания маппинга, потому что если мы будем на лету делать динамический импорт, то весь UI Kit попадет к нам в приложение, и бандл станет слишком большим. Поэтому мы долго искали решение для этой проблемы.
Решили вышеупомянутым маппингом: создали объект, куда лениво импортируем наши компоненты. Да, работы больше чем на NuxtJS, но оно того стоит. Единственное, что нужно учесть — это fallback с пустым компонентом, если его нет в нашей карте. Чтобы не получить ошибку рендера, можно вернуть null.
В NextJS логика более разветвленная. Здесь мы идем в карту динамических компонентов, проверяем, что этот компонент существует и он не null, проверяем, есть ли данные для этого компонента. Если всё хорошо, тогда мы возвращаем сам компонент, ключ для него и данные, из которых мы его будем собирать.
Так как на предыдущем шаге мы получили массив компонентов, мы можем на лету перебрать их и отрендерить в страницу.
Кода получается чуть больше, но в целом система одинаковая на NuxtJS и NextJS. Поэтому выбирайте фреймворк, с которым привыкли работать.
На что обратить внимание:
Тщательный выбор хостинга/сервера.
Настройка современных протоколов.
Желательно под капотом иметь хотя бы HTTP/2, потому что последний бандл NuxtJS и NextJS содержит большое количество файлов. Идеально — подкрутить до третьего HTTP. Тогда по UDP-протоколу браузер будет сам решать, какой контент загрузить для отображения в первую очередь. С таким решением сайты работают очень быстро.Современные форматы сжатия.
Необходимо постепенно уходить от Xip в сторону Brotli, который отдает чуть меньше трафика. Если использовать его вместе с протоколом современного формата сжатия, у нас получится значительно ускорить отдачу контента пользователям.
Итого
Когда вам пригодится Headless CMS:
При необходимости развернуть Mock API.
Нас пару раз спасал Headless, когда нужно было развернуть Mock API сервер, а FireMock работать не хотел.Нужно быстро проверить теорию и запустить MVP.
Но учитывайте, что временное может остаться навсегда.Для упрощения процесса разработки.
Бывает, что у компании нет возможности предоставить бэкенд-ресурсы на разработку той или иной фичи. С Headless CMS можно зациклить процесс на фронтенд-разработчиках без дополнительных трудозатрат.
P. S. Делитесь своим мнением о безголовых CMS в комментариях. С какими из них работали, что получалось, а что нет. Буду рад вопросам.
А еще подписывайтесь на наш канал про тимлидство, скиллы и технологии — https://t.me/ashutay.
Что еще почитать
Комментарии (3)
Pitcentr0
11.06.2024 16:43все удобно все красиво но когда начинаются вопросы СЕО и пререндеринга, уже не все так удобно
up7
У WordPress достаточно много уязвимостей, и поэтому даже простой бэкэнд на нем не совсем безопасен.
Мне кажется, что вы не совсем корректно выразились. Из ваших слов можно подумать, что любой сайт на вордпрессе вы легко взломаете, так как там много уязвимостей.
Уязвимости есть везде, но ВордПресс, регулярно обновляемый , чистый - одна из самых безопасных CMS.
Самое главное - это кто программирует, кто кастомизирует систему. Неопытный программист, я уверен, и в вашей безголовой cms оставит кучу дыр.
skazi_premiere Автор
Я не утверждаю что подход полностью безопасен, ваш коментарий вполне уместен, даже скажу что под капотом есть родные баги такие что "Мама не горюй", но если нам требуется просто API то наверное WP будет излишен и менее удобен для контент менеджера особенно для построения динамических страниц.