Sanity — це CMS, до якої ми тягнемось за замовчуванням на Next.js маркетингових сайтах. Не єдина хороша відповідь. Payload — чудовий, якщо хочете database-backed контент з auth з коробки. Contentful — нормально, якщо ваша команда нетехнічна, і вас не дратує вищий місячний рахунок. Strapi — розумний вибір, якщо self-hosting — жорстка вимога.
Але для більшості маркетингових сайтів з командою розробки Sanity влучає у баланс, який нам важко перемогти: справжнє моделювання контенту, редакторський досвід, який редакторам справді подобається, вбудована Studio, що живе поруч із кодовою базою, і ціна, яка не карає вас за додавання seat-ів.
Це налаштування, яке ми відвантажуємо. Рішення, компроміси, речі, про які жалкуємо, що не знали раніше.
Чому Sanity, а не альтернативи
Вирішальні фактори, у порядку, наскільки часто вони реально мають значення:
Schema-as-code. Моделі контенту Sanity живуть у вашому репо як TypeScript. Версіоновані, з ревью, деплояться разом із рештою коду. Порівняйте з Contentful, де зміни схеми відбуваються у UI і виходять з синхрону з тим, що очікує код.
Portable Text реально кращий за rich-text поля. Більшість CMS дає вам WYSIWYG, що виплюнув HTML. Sanity дає Portable Text — структурований JSON-формат. Різниця з'являється, коли треба рендерити той самий контент по-різному в різних контекстах (тіло блог-посту, сніпет результату пошуку, OG-зображення). Структурований виграє у HTML-рядка щоразу.
Вбудована Studio. Sanity Studio — це React-застосунок. Можна запустити як окремий застосунок або вшити у Next.js-сайт за маршрутом /studio. Вшита означає, що редактори редагують контент поруч із сайтом, з live preview, без окремого деплою.
GROQ. Мова запитів Sanity. Година на вивчення. Виграє у GraphQL для типів запитів, які реально запускають маркетингові сайти (join, condition, fallback). Однієї функції coalesce() достатньо, щоб виправдати криву навчання.
Ціна. Щедрий безкоштовний тариф. Платні — usage-based, не за редактора. Можна мати двадцять редакторів на free-тарифі, якщо обсяг контенту малий. Порівняйте з базою «$489/місяць за п'ять редакторів» у Contentful.
Випадки, коли Sanity не правильна відповідь: коли ваша команда цілком нетехнічна і трохи «інженерний» UX Sanity Studio її валить; коли потрібне вбудоване комерс-рішення і ви не хочете самі інтегрувати Stripe; коли вам конкретно потрібен self-hosting з комплаєнсу.
Рішення зі схемою, які мають значення
Як ви моделюєте контент у Sanity — це стеля для того, наскільки приємно буде через півроку. Кілька рішень, які ухвалите у першу годину:
Двомовний контент: дуальні поля чи локалі рівня документа
У Sanity два паттерни для багатомовного контенту. Ми використовуємо паттерн дуальних полів для маркетингових сайтів і рекомендуємо його.
Дуальні поля:
defineField({ name: "titleEn", title: "Title (EN)", type: "string" }),
defineField({ name: "titleUk", title: "Title (UK)", type: "string" }),
defineField({ name: "bodyEn", title: "Body (EN)", type: "array", of: [{ type: "block" }] }),
defineField({ name: "bodyUk", title: "Body (UK)", type: "array", of: [{ type: "block" }] }),Один документ тримає всі локалі. Редактори бачать обидва поля поруч у Studio. Запити використовують coalesce() для фоллбеку на дефолтну локаль, якщо переклад відсутній:
*[_type == "service" && slug.current == $slug][0]{
"title": coalesce(titleEn, titleUk),
"body": coalesce(bodyEn, bodyUk),
...
}Локалі рівня документа:
Один документ на локаль, з'єднані translation-референсом. Краще для багатьох локалей (5+), гірше для двох. З двома локалями паттерн дуальних полів простіший для роздумів, легший у синхронізації, дає швидші запити.
Для маркетингових сайтів у двох-трьох локалях: дуальні поля. Для продуктів у 10+ локалях: рівень документа.
Portable Text vs. markdown
Portable Text Sanity — структурований JSON. Кожен абзац, заголовок, елемент списку — це блок з типом, marks і children. Рендер — на вашій стороні.
Ми використовуємо Portable Text. Переваги:
- Кастомні типи блоків. У нас є блок
codeз підсвіткою синтаксису (через Shiki, рендериться на сервері). Є блокimageз hotspot і alt. Нативно у схемі, не вбудований HTML. - Передбачуваний рендеринг. Можна змінити рендерер один раз і оновити кожен блог-пост.
- Чистіші міграції. Коли проєкт змінює типографіку чи відступи, контент не потребує переписування.
Недолік: редакторам вивчити Portable Text займає на тридцять хвилин більше, ніж markdown. Ми ніколи не мали команди, яка б штовхалась проти після першої сесії.
Сінглтони vs. колекції
Деякий контент — один-в-одному екземплярі: налаштування сайту, герой головної, копірайт футера. Це сінглтони. У Sanity ви забезпечуєте сінглтонність у структурі desk, не у схемі:
S.listItem()
.title("Site Settings")
.id("siteSettings")
.child(S.editor().id("siteSettings").schemaType("siteSettings").documentId("siteSettings")),documentId фіксований. Редактори не зможуть випадково створити другий.
Колекції — багато-з-одного: блог-пости, послуги, кейси. Стандартний list view.
Помилка, яку ми бачимо: класти налаштування сайту всередину колекції (колекція «Settings» з одним документом). Працює, але Studio показує це як список з одним елементом, що збиває. Використовуйте сінглтони правильно з першого дня.
Слаги під локаль
Якщо ваші URL локалізовані (/en/services/landing-page і /uk/services/landing-page), слаг має відрізнятись під локаль. Sanity це підтримує:
defineField({ name: "slugEn", type: "slug", options: { source: "titleEn" } }),
defineField({ name: "slugUk", type: "slug", options: { source: "titleUk" } }),Не діліть один слаг між локалями. Якщо ви транслітеруєте слаги (EN-слаг — landing-page, UK-слаг — lendinh), обидва читачі отримують URL у знайомій формі. Краще SEO, краще шерінг у соцмережах.
Вбудована Studio у Next.js
Sanity Studio може жити за маршрутом /studio у вашому Next.js-застосунку. Налаштування:
- Встановіть
next-sanityіsanity. - Створіть
sanity.config.tsу корені репо зі схемами, плагінами і project ID. - Додайте маршрут
app/studio/[[...tool]]/page.tsx, що рендеритьNextStudio. - Закрийте маршрут у проді (авторизація або robots.txt + noindex; краще авторизація).
Переваги перед окремим деплоєм Studio:
- Один деплой, одне репо. Редактори і розробники у тій самій кодовій базі.
- Live preview працює без проблем cross-domain-авторизації.
- Зміни схеми відвантажуються разом зі змінами коду. Жодних багів «Studio десинхронізований із сайтом».
Недолік: бандл маркетингового сайту трохи більший, якщо не зробити обережний code split маршруту Studio. Studio — важка. Переконайтесь, що Studio вантажиться лише коли хтось заходить на /studio, не на кожному завантаженні сторінки.
Live preview і Draft Mode
Редактори хочуть бачити зміни до публікації. Draft Mode Next.js плюс preview pane Sanity справляються з цим:
- Редактор редагує документ у Studio.
- Draft Mode увімкнений (через API-маршрут, що ставить cookie).
- Сторінки Next.js, коли Draft Mode увімкнений, запитують Sanity з perspective
drafts. - Редактор бачить чернетку живою, у реально відрендереній сторінці, не у preview pane Studio.
- Публікація у Studio комітить зміну; tag-based revalidation освіжає продакшн-сторінку за секунди.
Дві частини, які треба підключити:
// API-маршрут, що вмикає Draft Mode
export async function GET(request: Request) {
const { isValid, redirectTo } = await validateToken(request);
if (!isValid) return new Response("Unauthorised", { status: 401 });
(await draftMode()).enable();
redirect(redirectTo || "/");
}// Запит сторінки
const { isEnabled: isDraftMode } = await draftMode();
const data = await client.fetch(query, params, {
perspective: isDraftMode ? "drafts" : "published",
});Token-based авторизація на маршруті ввімкнення Draft Mode — не обговорюється. Без неї будь-хто може перемкнути ваш сайт у режим чернеток.
Продуктивність: tag-based revalidation
Дефолтне налаштування Sanity використовує CDN-кеш Sanity. Працює, але має компроміси (лаг інвалідації кешу, менше контролю). Для маркетингових сайтів, де редактори хочуть, щоб зміни йшли у прод за секунди, ми надаємо перевагу tag-based revalidation Next.js.
Кожен GROQ-запит отримує тег:
const data = await client.fetch(query, params, {
next: { tags: ["blog"] },
});На стороні Sanity webhook вистрілює при змінах документа. Webhook потрапляє у маршрут Next.js, що викликає revalidateTag():
export async function POST(request: Request) {
const { _type } = await request.json();
const tagMap = { blogPost: "blog", service: "services", siteSettings: "settings" };
revalidateTag(tagMap[_type]);
return Response.json({ revalidated: true });
}Цей паттерн дає миттєві оновлення (менше 2 секунд від публікації до прода) без ребілду сайту. Сторінки лишаються закешованими в іншому разі, що і є правильним дефолтом.
Підпис webhook треба верифікувати. Sanity надсилає HMAC-заголовок; перевіряйте його перед обробкою webhook.
Чого Sanity не вміє добре
Чесно про шорсткі краї:
Менеджмент image-ассетів. Завантаження зображень у Sanity нормальне, але UX бібліотеки ассетів утилітарний. Якщо ваші редактори завантажують 50+ зображень на тиждень і хочуть поліровану медіа-бібліотеку, Sanity відчуватиметься недостатнім. Інтеграція з Cloudinary — обхідний шлях.
Bulk-операції. Оновлення 100 документів одночасно потребує Node-скрипта. Studio не виставляє bulk-операції у UI. Для більшості команд це нормально. Для контент-важких сайтів з частими bulk-правками це точка тертя.
Ціна для високого обсягу контенту. Free-тариф комфортно тримає малі маркетингові сайти. Щойно ви переходите ліміти документів або API-запитів, вмикається платний. Для 50-сторінкового маркетингового сайту з помірним трафіком ви під free-тарифом. Для бібліотеки контенту у 5000 документів з мільйонами API-хітів рахунок масштабується.
Реальний час спільної роботи над одним документом. У Sanity є real-time оновлення, але два редактори, що друкують у тому самому полі одночасно — не чудовий досвід. Google Docs це не є. Для більшості маркетингових команд це ніколи не має значення.
Налаштування, яке ми відвантажуємо
Від початку до кінця Sanity-налаштування, яке ми відвантажуємо на Next.js маркетинговому сайті:
- Schema as code з паттерном дуальних полів для двомовного контенту
- Вбудована Studio за
/studioз авторизацією - Tag-based revalidation через webhook (менше 2 секунд від публікації до прода)
- Draft Mode з токенною авторизацією для прев'ю
- Кастомні Portable Text-рендерери (код з Shiki, зображення з hotspot, списки, заголовки)
- Сінглтони для налаштувань сайту через структуру desk
- GROQ-запити, колокальовані з компонентами, що їх використовують, з
coalesce()для фоллбеку локалі
Якщо хочете маркетинговий сайт із цим налаштуванням як фундаментом, подивіться, як ми працюємо із сайтами. Кастомно зі схеми вгору, не Webflow-шаблон, на який кинули ваш логотип.