За последний год ко мне несколько раз приходили с одной и той же задачей: нужен лендинг на 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.