Diil Docs
  1. Документация
  2. С чего начать

Как устроен контент: страницы, секции, блоки

Обновлено:

Прежде чем писать первый fetch, полезно понять, как Diil смотрит на сайт. Хорошая новость: точно так же, как вы. Сайт — это набор страниц, страница — стопка секций, секция — несколько блоков: тут заголовок, там картинка, внизу список вопросов и ответов. Запомните эти три слова — и весь API уложится в голове.

Модель в одной строке: страница → секция → блок

Всё, что владелец сайта правит в CRM (или прямо на живом сайте в режиме Live), складывается в одно дерево. Ваш сайт читает это дерево через Content API — только чтение, никаких записей — и выводит как угодно: вёрстка, стили и фреймворк целиком ваши.

  • Страница — одна страница сайта: home, pricing, contacts. У неё есть название, адрес (href), SEO-параметры и секции.
  • Секция — горизонтальный кусок страницы: первый экран, сетка преимуществ, FAQ. Она объединяет блоки, у неё есть флаг show и позиция (index).
  • Блок — самая маленькая редактируемая единица: заголовок, картинка, цена, ссылка на кнопке, целый список отзывов. У каждого блока есть type, и от него зависит, как выглядит content.

Блог живёт рядом с этим деревом, а не внутри: у статей свои запросы, свои адреса (slug) и постраничная выдача. Подробности — в разделе Блог.

Разбираем настоящую страницу

Возьмём главную VERTEX — небольшого магазина беспроводных наушников. На глаз там большой первый экран с заголовком и фото товара, ряд преимуществ и FAQ внизу. В Diil это выглядит так:

Дерево главной страницы VERTEXtext
page  home
├── section  hero        index 0, show: true
│   ├── block  hero_title     text    "Earbuds that mute the city"
│   └── block  hero_image     image   hero.jpg
├── section  features    index 1, show: true
│   └── block  features_list  array   [ {…}, {…}, {…} ]
└── section  faq         index 2, show: true
    └── block  faq_section    object  { title, items: [ … ] }

А вот что на неё отвечает GET /v1/pages/home (секция features сокращена):

curl "https://back.sitecog.com/content/v1/pages/home?lang=en,de" \
  -H "x-crm-key: pk_3f9c2a7e1b4d4c0e8a6f5b2d9c1e7a40"
200 OKjson
{
  "id": 26,
  "marker": "home",
  "name": "Home",
  "href": "/",
  "index": 0,
  "params": {
    "title": { "en": "VERTEX Air 3 — wireless earbuds", "de": "VERTEX Air 3 — kabellose Ohrhörer" }
  },
  "content": {
    "hero": {
      "id": 41,
      "marker": "hero",
      "name": "Hero",
      "index": 0,
      "show": true,
      "content": {
        "hero_title": {
          "id": 95,
          "marker": "hero_title",
          "name": "Hero title",
          "type": "text",
          "multilang": true,
          "updatedAt": "2026-09-20T16:33:23.000Z",
          "content": {
            "en": "Earbuds that mute the city",
            "de": "Ohrhörer, die die Stadt leiser machen"
          }
        },
        "hero_image": {
          "id": 96,
          "marker": "hero_image",
          "name": "Hero image",
          "type": "image",
          "multilang": false,
          "updatedAt": "2026-09-18T09:12:40.000Z",
          "content": { "file": "https://cdn.example.com/storage/your-site/hero.jpg" }
        }
      }
    },
    "features": {
      "id": 42,
      "marker": "features",
      "name": "Features",
      "index": 1,
      "show": true,
      "content": { "…": "…" }
    },
    "faq": {
      "id": 44,
      "marker": "faq",
      "name": "FAQ",
      "index": 2,
      "show": true,
      "content": {
        "faq_section": {
          "id": 102,
          "marker": "faq_section",
          "name": "FAQ",
          "type": "object",
          "multilang": false,
          "updatedAt": "2026-09-22T11:05:10.000Z",
          "content": {
            "title": { "en": "FAQ", "de": "Häufige Fragen" },
            "items": [
              {
                "question": { "en": "How long is delivery?", "de": "Wie lange dauert der Versand?" },
                "answer": { "en": "1–3 days.", "de": "1–3 Tage." }
              },
              {
                "question": { "en": "Do they work with iPhone?", "de": "Funktionieren sie mit dem iPhone?" },
                "answer": { "en": "Yes, and with Android too.", "de": "Ja, und auch mit Android." }
              }
            ]
          }
        }
      }
    }
  }
}

Если прочитать ответ сверху вниз, закономерность бросается в глаза:

  • content страницы — объект секций по маркерам.
  • content секции — объект блоков по маркерам.
  • content блока — само значение, форма которого зависит от type.

Поэтому немецкий вариант первого вопроса из FAQ — это page.content.faq.content.faq_section.content.items[0].question.de. Длинно? Да. Неожиданно? Никогда.

Маркеры — имена, на которые можно положиться

К страницам, секциям и блокам обращаются по маркерам — коротким машинным именам, которые редактор или разработчик задаёт в CRM. home, hero и hero_title из примера выше — всё это маркеры. Их вы подставляете в адрес запроса (/v1/pages/home), и они же служат ключами объектов в ответе.

Правила

  • Только латиница, цифры и подчёркивание: ^[A-Za-z0-9_]{2,40}$.
  • Длина — от 2 до 40 символов.
  • Регистр важен: Hero и hero — два разных маркера.
  • Всё остальное в адресе получает 400 {"message":"invalid_marker"} ещё до того, как мы начнём искать.
  • Маркер блока уникален внутри секции, а не на всём сайте. Блок title вполне может быть в двух секциях сразу.

Как называть, чтобы потом не пожалеть

  • snake_case, строчными. hero_title, а не HeroTitle и не heroTitle2. Регистр имеет значение, так что единый стиль спасает от «почему тут undefined» в два часа ночи.
  • Начинайте маркер блока с имени секции. hero_title, hero_image, faq_section. Голый title внутри ответа страницы никому не мешает, но стоит запросить его отдельно через /v1/blocks — и начинается угадайка: без ?section побеждает самый старый блок с таким маркером.
  • Называйте по смыслу, а не по виду. promo_banner переживёт редизайн, red_box_left — нет.
  • Не переименовывайте маркеры между делом. На них завязан ваш код. Переименовать маркер в CRM — всё равно что переименовать колонку в боевой базе.

Почему маркеры, а не id?

Числовой id у каждого объекта тоже есть, смотреть на него никто не запрещает. Но id выдаёт база данных: на разных сайтах и в разных окружениях они разные, а тому, кто будет читать ваш код, не говорят ровным счётом ничего. Маркеры придумывают люди, и читаются они как документация: page.content.hero понятен без комментариев, sections[41] — нет. Пишите шаблоны на маркерах, а id считайте справочной информацией.

Страница common: шапка, подвал и всё общее

Часть контента нужна на каждой странице: логотип, меню, телефон в шапке, ссылки в подвале. Обычно для этого заводят служебную страницу с маркером common и складывают такие блоки туда. Её href равен null — сама по себе она никогда не открывается, — а запрашивают её рядом с текущей страницей:

Данные для макета: текущая страница + commonjs
const headers = { 'x-crm-key': 'pk_3f9c2a7e1b4d4c0e8a6f5b2d9c1e7a40' };

const [page, common] = await Promise.all([
  fetch('https://back.sitecog.com/content/v1/pages/home?lang=en', { headers }).then((r) => r.json()),
  fetch('https://back.sitecog.com/content/v1/pages/common?lang=en', { headers }).then((r) => r.json()),
]);

const footer = common.content.footer.content; // блоки, общие для всех страниц

Оба ответа кэшируются, так что лишний запрос почти ничего не стоит. Это договорённость, а не магия: если вашей команде милее layout или shared, API возражать не станет.

Языки — это карты, а не копии

Diil не хранит «английскую страницу» и «немецкую страницу» по отдельности. Страница одна, а каждое переводимое значение — карта переводов:

{ "en": "Earbuds that mute the city", "de": "Ohrhörer, die die Stadt leiser machen" }
  • Без ?lang приходят все активные языки сайта. С ?lang=en или ?lang=en,de — только запрошенные.
  • Ключи всегда идут в том порядке, который задан в CRM, в каком бы порядке вы их ни просили.
  • Запасного языка на сервере нет. Если перевода нет, языка просто не будет в карте блока, а пустой перевод придёт как "". Какой язык подставить вместо недостающего — решаете вы; маленький помощник для этого есть в разделе Языки и запасной язык.
  • Сам список языков отдаёт /v1/langs — ровно то, что нужно переключателю языка.

Типы блоков — коротко

type блока подсказывает, чего ждать в его content. Ниже шпаргалка, а все типы с полными примерами — на странице Типы блоков.

ТипКак выглядит contentДля чего обычно
text{ "en": "…", "de": "…" }Заголовки, короткие тексты
html{ "en": "<p>…</p>" } — значения в виде HTMLТекст с форматированием
image, video{ "file": "https://…" } или { "multilang": true, "en": "…", "de": "…" }, если у каждого языка свой файлФото товара, баннеры, промо-ролики
link{ "url": "…", "target": "_self" | "_blank", "title": { "en": "…" } }Кнопки, пункты меню
number, color, booleanЗначение как есть или null: 149, "#3D3D5C", trueЦены, фирменные цвета, переключатели
date, date_rangeЗначение в том виде, в каком его ввели в CRM, или nullСроки акций, события
objectПоля по маркерам; каждое поле подчиняется правилам вышеКарточка, блок FAQ с заголовком
arrayJSON-массив элементов, устроенных как объект, в порядке из CRMСписки преимуществ, отзывы, вопросы FAQ

Скрытые секции и флаг show

Редактор может выключить секцию в CRM — например, спрятать FAQ, пока его переписывают. Тогда у секции show: false, но в ответе она всё равно есть, со всеми блоками. Что с ней делать, решает ваш шаблон:

{page.content.faq?.show && <Faq data={page.content.faq.content} />}

Забудете проверку — и скрытая секция бодро появится на живом сайте. API сообщает, ваш код решает.

Порядок и поле index

У страниц и секций есть index — позиция в CRM, начиная с 0. Объекты в ответе обычно уже идут в этом порядке, но честнее всего восстанавливать то, что видит редактор, сортировкой по index — особенно если секции выводятся динамически:

const sections = Object.values(page.content)
  .filter((section) => section.show)
  .sort((a, b) => a.index - b.index);

sections.forEach((section) => render(section.marker, section.content));

У элементов внутри блока array поля index нет: порядок массива и есть порядок из CRM. Статьи блога идут так, как выбрано в настройках блога в CRM.

Какой запрос выбрать?

Коротко: берите страницу целиком, если нет причин поступить иначе. Подробно:

Что нужноЗапросПочему
Всё для вывода страницыGET /v1/pages/:markerОдин запрос — все секции и блоки. Выбор по умолчанию.
Меню или карта сайтаGET /v1/pagesВсе страницы с href и SEO-параметрами, без содержимого.
Одна секция — скажем, промо-полоса, которая повторяется на нескольких страницахGET /v1/sections/:markerОтвет меньше, когда остальная страница берётся из другого места.
Одно значение — телефон, баннер, ценаGET /v1/blocks/:marker?section=…Ровно один блок. Передайте section, чтобы не промахнуться.
Список статей блога или одна статьяGET /v1/blog, /v1/blog/:slugСтатьи живут вне дерева страниц, и у них есть постраничная выдача.
Языки сайтаGET /v1/langsПереключатель языка, теги hreflang.

Что дальше