Разработка · 8 мин чтения

Лендинг на Next.js с интеграцией Strapi: гайд по headless CMS

За последний год ко мне несколько раз приходили с одной и той же задачей: нужен лендинг на Next.js с интеграцией Strapi, чтобы маркетолог сам менял тексты, цены и блоки на сайте, а разработчик не тратил время на правки вёрстки после каждого письма от клиента. Хардкодить контент в JSX удобно, пока страница не начинает жить своей жизнью - цены меняются, отзывы добавляются, баннеры под акции сыпятся каждую неделю. Strapi в связке с Next.js закрывает это без переезда на WordPress и без потери скорости статической генерации.

Зачем лендингу на Next.js отдельная CMS, если можно хардкодить контент

На ранних проектах хардкодил тексты прямо в компонентах - нормально для MVP, но через месяц-два клиент присылает пятое письмо с правками цены и просит поменять слово в заголовке блока преимуществ. Каждая такая правка - это коммит, деплой, минимум 10-15 минут разработчика, даже если сама правка на 30 секунд. На Tilda эту проблему решает визуальный редактор, но там быстро упираешься в кастомную логику: сложные формы с валидацией, интеграции с CRM, приём оплаты через эквайринг вроде T‑Bank - приходится городить кастомные скрипты поверх конструктора. Next.js даёт полный контроль над версткой и скоростью (Lighthouse 90+ из коробки при аккуратной сборке), но без CMS контент снова превращается в код.

Strapi встаёт между этими крайностями: у клиента остаётся админка с понятными полями - заголовок, картинка, список преимуществ, а фронт как и раньше собирается статикой или обновляется по требованию через ISR. Разработчик не трогает JSX ради правки текста, редактор не трогает код вообще.

Устанавливаю Strapi и поднимаю окружение

Беру Strapi v5 - на v4 тоже собирал лендинги, но пятая версия удобнее с Dynamic Zone и TypeScript из коробки. Node нужен 18.x или 20.x, на 22‑й версии на момент написания статьи иногда вылезают проблемы с зависимостями. Ставлю через pnpm, чтобы не тащить лишний вес в node_modules:

pnpm dlx create-strapi-app@latest cms --quickstart --no-run
cd cms
pnpm develop

Флаг - quickstart поднимает SQLite для локальной разработки - этого достаточно, чтобы спроектировать структуру контента. В проде переключаю на PostgreSQL: для лендинга это не про нагрузку, а про нормальные бэкапы и миграции без риска потерять файл базы. В .env прописываю APP_KEYS, API_TOKEN_SALT, ADMIN_JWT_SECRET, TRANSFER_TOKEN_SALT - Strapi генерирует их сам при первом запуске, но для прода перегенерирую заново, а не тащу значения из локального окружения в git.

После первого запуска захожу в /admin, создаю аккаунт администратора - с этого момента структура контента настраивается через UI, без единой миграции руками.

Проектирую Content Types под блоки лендинга

Для одностраничника завожу Single Type «Landing Page» - у него не будет множества записей, только один набор данных на весь сайт. Внутри - Dynamic Zone из компонентов: hero, advantages, pricing, testimonials, faq, contacts. Каждый компонент - переиспользуемая структура полей, которую потом можно вставить в другой лендинг того же клиента без копирования кода на фронте.

Например, компонент pricing.tariff-card содержит name, price, features (repeatable-компонент), is_featured (boolean) - на фронте это просто маппится в карточку тарифа. Если через полгода клиент попросит второй лендинг под другой продукт, схему не переписываю заново, а собираю новую страницу из тех же компонентов в другом порядке.

Для медиа настраиваю поле типа Media с ограничением по типам файлов - иначе редактор рано или поздно зальёт PDF вместо картинки в hero-баннер, и вёрстка на фронте посыпется. Права доступа проверяю сразу: в Settings → Roles → Public включаю find и findOne только для тех Content Types, которые действительно должны быть публичными, - админку никогда не открываю наружу без токена.

Подключаю Strapi к Next.js: получаю контент и обновляю страницу

Запрашиваю контент через REST API

Strapi отдаёт REST API из коробки по адресу /api/landing-page с параметром populate для вложенных полей и компонентов. По умолчанию связи отдаются только на один уровень, поэтому для Dynamic Zone указываю populate=deep или собираю кастомный query через qs.

async function getLandingPage() {
  const res = await fetch(
    `${process.env.STRAPI_URL}/api/landing-page?populate=deep,3`,
    {
      headers: { Authorization: `Bearer ${process.env.STRAPI_API_TOKEN}` },
      next: { revalidate: 3600 },
    }
  );
  if (!res.ok) throw new Error('Strapi request failed');
  const { data } = await res.json();
  return data;
}

Токен беру из API Tokens в настройках Strapi с правами read-only - этого достаточно для лендинга, писать в CMS с фронта не нужно. revalidate: 3600 держит страницу свежей раз в час даже без ручной ревалидации.

Настраиваю ISR-ревалидацию по вебхуку из Strapi

Когда клиент правит цену и ждёт, что она обновится сразу, часового кэша мало. В Strapi в Settings → Webhooks добавляю событие entry.publish на нужный Content Type с URL на роут в Next.js:

import { revalidatePath } from 'next/cache';
import { NextRequest, NextResponse } from 'next/server';

export async function POST(req: NextRequest) {
  const secret = req.headers.get('x-webhook-secret');
  if (secret !== process.env.REVALIDATE_SECRET) {
    return NextResponse.json({ message: 'Invalid secret' }, { status: 401 });
  }
  revalidatePath('/');
  return NextResponse.json({ revalidated: true });
}

Секрет прокидываю через кастомный заголовок в настройках вебхука Strapi, чтобы роут не мог дёрнуть кто угодно. Похожий обработчик собирал не один раз для разных клиентов - под частые кейсы, вроде ревалидации по вебхуку или уведомления в Telegram через aiogram-бота о новой заявке с лендинга, держу заготовки в библиотеке готовых скриптов, чтобы не писать одно и то же заново под каждый проект. Экономит время на рутине, а не на архитектуре самого лендинга.

Бесплатный материал

🎁 Полезный скрипт в подарок

Подпишитесь на Telegram - пришлю готовый скрипт по этой теме.

Без спама. Отписка в 1 клик.

Деплой связки: Vercel для фронта, отдельный хостинг для Strapi

Next.js отправляю на Vercel - там ISR и ревалидация из коробки, деплой по пушу в main. Strapi на serverless не размещаю: он рассчитан на постоянно работающий процесс с файловой системой и подключением к базе, поэтому беру VPS (обычно хватает 2 CPU / 4 GB RAM для лендинга с несколькими Content Types) и разворачиваю через Docker Compose - Strapi, PostgreSQL и Nginx с SSL через Let’s Encrypt.

Для загруженных картинок сразу подключаю S3-совместимое хранилище - Object Storage у Selectel или Yandex Cloud, через провайдер strapi-provider-upload-aws-s3. Если оставить дефолтное хранение на диске VPS, при следующем редеплое или переезде на другой сервер все картинки из лендинга придётся заливать заново вручную - сталкивался с этим один раз и больше так не делаю.

Админку Strapi (/admin) закрываю от индексации и по возможности ограничиваю по IP через Nginx - заявок на перебор пароля админа на конструкторских сайтах видел достаточно, чтобы не оставлять её открытой всем подряд.

Сравниваю headless CMS для лендинга: Strapi, WordPress, Tilda

Выбор между тремя вариантами обычно упирается не в технологии, а в то, кто и как часто будет менять контент после запуска.

Критерий Strapi + Next.js WordPress Tilda
Скорость страницы статика/ISR, Lighthouse 90+ зависит от темы и плагинов, обычно 60-80 быстро на встроенном хостинге, кастомный код замедляет
Гибкость дизайна полный контроль, любой JSX ограничена темой и конструктором блоков ограничена блоками платформы
Кто редактирует контент маркетолог через понятную админку маркетолог, привычный интерфейс маркетолог, визуальный редактор
Стоимость запуска у меня от 80 000 ₽ от 60 000 ₽ от 30 000 ₽
Когда выбираю нужен кастомный дизайн и API на будущее - приложение, бот нужен привычный интерфейс и много готовых плагинов нужен быстрый запуск без разработки с нуля

Типичные грабли при интеграции Strapi и Next.js

На третьем-четвёртом проекте с этой связкой набрался список ошибок, которые повторяются почти у всех, кто делает такую интеграцию впервые.

  • CORS - Strapi по умолчанию режет запросы с чужого домена, в config/middlewares.js нужно явно прописать домен фронта, иначе в проде запросы падают, хотя локально всё работает через localhost.
  • next.config.js без домена картинок - Strapi отдаёт URL на медиа со своего хоста, и Next Image требует явно добавить его в images.remotePatterns, иначе сборка падает с ошибкой.
  • Пустой populate - если не указать вложенность, Dynamic Zone и связанные компоненты приходят пустыми, а не с ошибкой, из-за этого дольше всего ищешь баг.
  • Draft & Publish - черновики по умолчанию не отдаются публичным API, и если редактор сохранил, но не нажал Publish, на лендинге ничего не поменяется, хотя в админке всё выглядит готово.
  • Забытые права Public-роли - после создания нового Content Type права find не включены по умолчанию, запрос с фронта возвращает 403, хотя токен настроен верно.
  • Rich Text как blocks в Strapi v5 - формат хранения сменился с markdown на структурированный JSON, старый рендерер с проекта на v4 без переписывания не подойдёт.

Сколько стоит и сколько занимает такая интеграция на практике

Лендинг на Next.js с интеграцией Strapi у меня стоит от 80 000 ₽ - в сумму входит разработка компонентов страницы, настройка Content Types под структуру блоков клиента, подключение API с ревалидацией и деплой обеих частей. Срок обычно 2-3 недели: неделя на дизайн и вёрстку блоков, ещё неделя-полторы на CMS, интеграцию и тесты форм. Если добавляется приём оплаты через эквайринг или интеграция с CRM, доработку считаю отдельно - от 40 000 ₽ сверху, в зависимости от сложности API партнёра.

На рынке студии за похожую связку с кастомным дизайном и headless CMS обычно просят от 150 000 до 350 000 ₽ - разброс сильно зависит от количества правок в процессе и от того, работают ли над проектом несколько человек одновременно. После запуска беру техподдержку от 15 000 ₽/мес - если клиент планирует часто менять структуру блоков, а не только тексты внутри готовых полей, дешевле держать разработчика на подхвате, чем каждый раз объяснять новому фрилансеру логику Dynamic Zone.

Быстрый SEO-лендинг под продукт

Лендинг на Next.js

от 80 000 ₽

Подробнее →

Частые вопросы

Нужен ли отдельный сервер под Strapi, если фронт уже на Vercel?

Да, Strapi нужно где-то держать постоянно запущенным - на serverless-хостинге вроде Vercel он не заработает так, как задуман. Обычно достаточно VPS с 2 CPU и 4 GB RAM или управляемого хостинга вроде Railway - для лендинга нагрузка минимальная, ресурсов с запасом.

Можно ли подключить Strapi к уже готовому лендингу на Next.js?

Можно, если лендинг верстался компонентами с понятной структурой пропсов - тогда просто меняю источник данных с хардкода на fetch к API. Если вёрстка монолитная без разделения на блоки, дешевле и быстрее переписать компоненты под Dynamic Zone заново, чем подгонять API под старую структуру.

Чем Strapi лучше WordPress для лендинга?

Строгой структурой контента и скоростью отдачи API - WordPress тянет за собой движок для рендеринга страниц, который в headless-режиме не нужен, а Strapi изначально спроектирован как API-first. Для лендинга с нестандартным дизайном это ощутимо: меньше костылей на стороне фронта и понятнее схема данных для редактора.

Как сделать мультиязычный лендинг на такой связке?

В Strapi включаю плагин Internationalization, добавляю нужные локали и дублирую записи по языкам через встроенный переключатель в админке. На фронте роуты делаю через /[locale]/ и передаю параметр locale в запрос к API - Strapi сам фильтрует контент по нужному языку без дополнительной логики на стороне Next.js.

Есть задача?

Обсудим в мессенджере

Расскажите, что нужно сделать — отвечу в течение 4 часов в рабочее время. Первая консультация бесплатно.

Самозанятый Калинкин Н. А. · работаю с физлицами и юрлицами

Продолжая пользование настоящим сайтом Вы выражаете своё согласие на обработку Ваших персональных данных (файлов куки) с использованием Yandex.Metrika.
Понятно