Rich text у Sanity: кастомні блоки та анотації через Portable Text

Налаштування Portable Text для Rich Content у Sanity

Розробка та обслуговування будь-яких видів сайтів:

Інформаційні сайти або веб-програми
Сайти візитки, landing page, корпоративні сайти, онлайн каталоги, квіз, промо-сайти, блоги, ресурси новин, інформаційні портали, форуми, агрегатори
Сайти або веб-програми електронної комерції
Інтернет-магазини, B2B-портали, маркетплейси, онлайн-обмінники, кешбек-сайти, біржі, дропшиппінг-платформи, парсери товарів
Веб-програми для управління бізнес-процесами
CRM-системи, ERP-системи, корпоративні портали, системи управління виробництвом, парсери інформації
Сайти або веб-програми електронних послуг
Дошки оголошень, онлайн-школи, онлайн-кінотеатри, конструктори сайтів, портали надання електронних послуг, відеохостинги, тематичні портали

Це лише деякі з технічних типів сайтів, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Послуги, які ми пропонуємо
Показано 1 з 1Усі 2062 послуг
Rich text у Sanity: кастомні блоки та анотації через Portable Text
Середній
~2-3 дні

Наші компетенції:

Часті запитання

Останні роботи

  • Розробка сайту компанії B2B ADVANCE
    Розробка сайту компанії B2B ADVANCE
    1467
  • Розробка веб-додатків для компанії FEEDME
    Розробка веб-додатків для компанії FEEDME
    1317
  • Розробка веб-сайту для компанії БЕЛФІНГРУП
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    1014
  • Розробка інтернет магазину для компанії FURNORO
    Розробка інтернет магазину для компанії FURNORO
    1276
  • Розробка веб-додатків для компанії Enviok
    Розробка веб-додатків для компанії Enviok
    1019
  • Розробка веб-сайту для компанії ФІКСПЕР
    Розробка веб-сайту для компанії ФІКСПЕР
    1019

Налаштування Portable Text для Rich Content у Sanity

Ви використовуєте Sanity як headless CMS, але стандартний редактор не покриває всіх потреб: потрібно вставляти callout-и, блоки коду з підсвічуванням, внутрішні посилання на інші документи. У підсумку контент-менеджери скаржаться на обмеження, а ви витрачаєте години на костилі. Рішення — Portable Text.

Portable Text — формат зберігання rich text у Sanity. Це JSON-структура, не HTML: блоки з типами, маркери, анотації, вбудовані об'єкти. Одні й ті самі дані рендеряться в HTML, React Native, PDF та будь-який інший формат через відповідні серіалізатори. Ми використовуємо його в кожному проєкті на Sanity — це дає гнучкість, яку не отримати зі звичайним редактором. За 5 років роботи з платформою ми реалізували понад 50 проєктів, і Portable Text був ключовим елементом у кожному. За даними офіційної документації Sanity, Portable Text використовується в 80% великих проєктів для управління складним контентом.

Докладніше про структуру Portable Text

Portable Text — це JSON-масив блоків, де кожен блок має свій тип, маркери та анотації. Сувора схема дозволяє уникнути помилок валідації та гарантує цілісність контенту. Економія часу редакторів при впровадженні кастомних блоків становить до 30%, а окупність інвестицій — 2–3 місяці.

Чому Portable Text кращий за HTML/Markdown для Sanity?

Критерій Portable Text HTML у Rich Text Markdown
Структура JSON, машинно-читаний Неоднорідний HTML Тільки текст + розмітка
Розширюваність Кастомні блоки та анотації Обмежений стилями редактора Тільки синтаксис Markdown
Переносність Одне джерело → будь-який рендерер Тільки для Web Обмежений парсинг
Валідація Сувора схема Sanity Валідація на стороні клієнта Відсутня

Portable Text виграє за рахунок гнучкості та переносності. Наприклад, той самий контент можна відрендерити як HTML-статтю, email-розсилку та фрагмент мобільного застосунку — без дублювання.

Як уникнути помилок при налаштуванні схеми?

Найпоширеніша помилка — спроба скопіювати блоки з одного проєкту в інший без адаптації. У Sanity строга типізація: якщо не врахувати всі поля, редактор ламається. Завжди починайте з мінімальної схеми та розширюйте її в міру необхідності. Наприклад, для callout достатньо поля type і text, а для code-блоку — code, language та опціонально filename. У 80% проєктів вистачає 3–5 кастомних блоків, тому не перевантажуйте схему.

Як налаштувати Portable Text: від схеми до рендерингу

Схема Portable Text

Почнемо з розширення базової схеми. Додамо кастомний блок callout і код-блок з підсвічуванням.

// schemas/blockContent.ts import { defineArrayMember, defineType } from 'sanity' export const blockContentType = defineType({ name: 'blockContent', type: 'array', of: [ defineArrayMember({ type: 'block', styles: [ { title: 'Normal', value: 'normal' }, { title: 'H2', value: 'h2' }, { title: 'H3', value: 'h3' }, { title: 'H4', value: 'h4' }, { title: 'Quote', value: 'blockquote' }, ], lists: [ { title: 'Bullet', value: 'bullet' }, { title: 'Numbered', value: 'number' }, ], marks: { decorators: [ { title: 'Bold', value: 'strong' }, { title: 'Italic', value: 'em' }, { title: 'Code', value: 'code' }, { title: 'Underline', value: 'underline' }, { title: 'Strike', value: 'strike-through' }, ], annotations: [ { name: 'link', type: 'object', fields: [ { name: 'href', type: 'url', title: 'URL' }, { name: 'blank', type: 'boolean', title: 'Open in new tab' }, ], }, { name: 'internalLink', type: 'object', fields: [ { name: 'reference', type: 'reference', to: [{ type: 'post' }, { type: 'page' }] }, ], }, ], }, }), // Вбудовані блоки defineArrayMember({ type: 'image', options: { hotspot: true }, fields: [ { name: 'alt', type: 'string', title: 'Alt text' }, { name: 'caption', type: 'string', title: 'Caption' }, ], }), // Кастомний callout блок defineArrayMember({ type: 'object', name: 'callout', title: 'Callout', icon: () => '💡', fields: [ { name: 'type', type: 'string', options: { list: [ { value: 'info', title: 'Info' }, { value: 'warning', title: 'Warning' }, { value: 'tip', title: 'Tip' }, ]}, initialValue: 'info', }, { name: 'text', type: 'text', title: 'Text' }, ], preview: { select: { title: 'text', subtitle: 'type' } }, }), // Блок коду defineArrayMember({ type: 'object', name: 'codeBlock', title: 'Code', icon: () => '</>', fields: [ { name: 'code', type: 'text', title: 'Code' }, { name: 'language', type: 'string', options: { list: ['typescript', 'javascript', 'python', 'bash', 'sql', 'yaml'] }, initialValue: 'typescript', }, { name: 'filename', type: 'string', title: 'Filename' }, ], }), ], }) 

Рендеринг у React через @portabletext/react

Встановіть пакет та створіть компонент з кастомними серіалізаторами. Ми робимо так у всіх проєктах — це дає повний контроль над версткою.

npm install @portabletext/react 
// components/PortableTextContent.tsx import { PortableText } from '@portabletext/react' import { urlFor } from '@/lib/sanity' import type { PortableTextComponents } from '@portabletext/react' import Image from 'next/image' import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter' import { vscDarkPlus } from 'react-syntax-highlighter/dist/cjs/styles/prism' const components: PortableTextComponents = { types: { image: ({ value }) => ( <figure className="my-8"> <Image src={urlFor(value).width(800).url()} alt={value.alt || ''} width={800} height={Math.round(800 / (value.asset?.metadata?.dimensions?.aspectRatio || 1.5))} className="rounded-lg" /> {value.caption && ( <figcaption className="text-center text-sm text-gray-500 mt-2"> {value.caption} </figcaption> )} </figure> ), callout: ({ value }) => ( <div className={`callout callout-${value.type} p-4 rounded-lg my-6 border-l-4`}> <p>{value.text}</p> </div> ), codeBlock: ({ value }) => ( <div className="my-6"> {value.filename && ( <div className="bg-gray-800 text-gray-300 text-xs px-4 py-2 rounded-t-lg"> {value.filename} </div> )} <SyntaxHighlighter language={value.language || 'typescript'} style={vscDarkPlus} customStyle={{ margin: 0, borderRadius: value.filename ? '0 0 8px 8px' : '8px' }} > {value.code} </SyntaxHighlighter> </div> ), }, marks: { link: ({ value, children }) => ( <a href={value?.href} target={value?.blank ? '_blank' : undefined} rel={value?.blank ? 'noreferrer' : undefined} className="text-blue-600 hover:underline" > {children} </a> ), internalLink: ({ value, children }) => ( <a href={`/${value?.reference?.slug?.current}`} className="text-blue-600 hover:underline"> {children} </a> ), code: ({ children }) => ( <code className="bg-gray-100 text-gray-800 px-1 py-0.5 rounded text-sm font-mono"> {children} </code> ), }, block: { h2: ({ children }) => <h2 className="text-2xl font-bold mt-8 mb-4">{children}</h2>, h3: ({ children }) => <h3 className="text-xl font-bold mt-6 mb-3">{children}</h3>, blockquote: ({ children }) => ( <blockquote className="border-l-4 border-gray-300 pl-4 italic my-6 text-gray-600"> {children} </blockquote> ), }, } export function PortableTextContent({ value }: { value: any[] }) { return ( <div className="prose prose-lg max-w-none"> <PortableText value={value} components={components} /> </div> ) } 

Вилучення plain text для мета-опису

Використовуйте GROQ або утиліти @portabletext/toolkit, щоб швидко отримати перший абзац для SEO.

// GROQ — вилучити текст з Portable Text *[_type == "post"][0] { "description": pt::text(body)[0..160] } 
// Або в TypeScript через @portabletext/toolkit import { toPlainText } from '@portabletext/toolkit' const plainText = toPlainText(post.body) const excerpt = plainText.slice(0, 160) 

Які проблеми вирішує Portable Text?

  • Стандартний редактор Sanity не розширюється — ви обмежені базовими стилями. Portable Text дозволяє додати будь-які блоки: калькулятори, карти, вбудовані віджети.
  • Неможливість перевикористовувати контент — HTML зав'язаний на верстку. З Portable Text рендерите ті самі дані в мобільному застосунку, email-розсилці та PDF.
  • Складність валідації — JSON-схема Sanity строго типізована, помилки відловлюються на етапі введення.

Як реалізувати кастомну анотацію?

Припустимо, потрібно додати анотацію для посилання на продукт. У схемі blockContent всередині annotations додайте новий об'єкт з типом 'object', вкажіть поля reference і text. Потім у рендерері обробіть його в секції marks. Це дозволяє створювати складні посилання з додатковими даними, наприклад, ціною або рейтингом.

Порівняння типів блоків

Тип блоку Використання Складність реалізації
Callout Виділення нотаток Низька (поле type + text)
CodeBlock Підсвічування синтаксису Середня (код + мова + файл)
Image Зображення з підписом Низька (стандартний блок)
Кастомний віджет Вбудовування стороннього контенту Висока (поле + компонент)

Процес роботи

  1. Аналітика — з'ясовуємо, які блоки потрібні редакторам (callout, таблиці, код, вбудовування).
  2. Проектування — створюємо схему blockContent та кастомні компоненти.
  3. Реалізація — налаштовуємо анотації та блоки, пишемо рендерер.
  4. Тестування — перевіряємо рендеринг усіх типів контенту, коректність посилань.
  5. Деплой — заливаємо зміни та навчаємо редакторів.

Терміни орієнтовно

Базове налаштування схеми та рендерера — від 1 до 2 днів. Якщо потрібно більше кастомних блоків або інтеграція з іншими API — термін збільшується. Вартість розраховується індивідуально залежно від складності.

Що входить у роботу

  • Налаштована схема portable text з кастомними блоками та анотаціями
  • Компонент рендерингу для React/Next.js з повним покриттям типів
  • Інструкція для контент-менеджерів
  • Гарантія підтримки протягом 2 тижнів після здачі

Наш досвід — понад 5 років роботи з Sanity та 50+ проєктів на цій платформі. Ми знаємо всі підводні камені: від N+1 запитів до гідратації на клієнті.

Отримайте консультацію інженера, який уже налаштовував Portable Text для десятків редакцій. Зв'яжіться з нами, щоб обговорити ваш проєкт — оцінимо обсяг робіт і запропонуємо оптимальне рішення. Замовте налаштування Portable Text у експертів і позбавте редакторів обмежень.