Перейти до вмісту
51studio
Туторіали

Sanity CMS для маркетингових сайтів: робоче налаштування

Автор: Sam Hollis12 хв читання

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 два паттерни для багатомовного контенту. Ми використовуємо паттерн дуальних полів для маркетингових сайтів і рекомендуємо його.

Дуальні поля:

ts
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() для фоллбеку на дефолтну локаль, якщо переклад відсутній:

groq
*[_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, не у схемі:

ts
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 це підтримує:

ts
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-застосунку. Налаштування:

  1. Встановіть next-sanity і sanity.
  2. Створіть sanity.config.ts у корені репо зі схемами, плагінами і project ID.
  3. Додайте маршрут app/studio/[[...tool]]/page.tsx, що рендерить NextStudio.
  4. Закрийте маршрут у проді (авторизація або 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 справляються з цим:

  1. Редактор редагує документ у Studio.
  2. Draft Mode увімкнений (через API-маршрут, що ставить cookie).
  3. Сторінки Next.js, коли Draft Mode увімкнений, запитують Sanity з perspective drafts.
  4. Редактор бачить чернетку живою, у реально відрендереній сторінці, не у preview pane Studio.
  5. Публікація у Studio комітить зміну; tag-based revalidation освіжає продакшн-сторінку за секунди.

Дві частини, які треба підключити:

ts
// 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 || "/");
}
ts
// Запит сторінки
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-запит отримує тег:

ts
const data = await client.fetch(query, params, {
  next: { tags: ["blog"] },
});

На стороні Sanity webhook вистрілює при змінах документа. Webhook потрапляє у маршрут Next.js, що викликає revalidateTag():

ts
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-шаблон, на який кинули ваш логотип.

Схожі статті