Diil Docs
  1. Документація
  2. З чого почати

Як влаштовано контент: сторінки, секції, блоки

Оновлено:

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

Модель в одному рядку: сторінка → секція → блок

Усе, що власник сайту редагує в CRM (або просто на живому сайті в режимі Live), потрапляє в одне дерево. Ваш сайт читає це дерево через Content API, доступний лише для читання, і рендерить його як завгодно — розмітка, стилі й фреймворк на 100% ваші.

  • Сторінка — одна сторінка вашого сайту: home, pricing, contacts. Має назву, шлях (href), SEO-параметри та свої секції.
  • Секція — горизонтальний зріз сторінки: hero, сітка переваг, FAQ. Групує блоки й має прапорець show і позицію (index).
  • Блок — найменша одиниця для редагування: заголовок, зображення, ціна, посилання кнопки, цілий список відгуків. Кожен блок має type, від якого залежить вигляд його content.

Блог живе поруч із цим деревом, а не всередині нього: дописи мають власні ендпоінти, slug і пагінацію. Детальніше — у розділі Блог.

Розбираємо справжню сторінку

Погляньмо на головну сторінку VERTEX — невеликого магазину бездротових навушників. Візуально на ній великий hero із заголовком і фото товару, ряд переваг і 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 вище — це все маркери. Саме їх ви підставляєте в URL (/v1/pages/home) і бачите як ключі об’єктів у відповідях.

Правила

  • Лише латинські літери, цифри та підкреслення: ^[A-Za-z0-9_]{2,40}$.
  • Довжина від 2 до 40 символів.
  • Регістр важливий: Hero і hero — два різні маркери.
  • Будь-що інше в URL отримує 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 — це як перейменувати колонку бази даних на production, тільки в контенті.

Чому маркери, а не id?

Кожен об’єкт має ще й числовий id, і ви можете на нього дивитися. Але id роздає база даних, тож вони різні на різних сайтах і в різних середовищах, і наступній людині, що читатиме ваш код, вони не кажуть нічого. Маркери обирають люди, і вони читаються як документація: page.content.hero пояснює сам себе, sections[41] — ні. Пишіть шаблони на маркерах, а id сприймайте як цікаву дрібницю.

Спільна сторінка: шапка, підвал і компанія

Деякий контент належить кожній сторінці: логотип, меню, телефон у шапці, посилання в підвалі. Звична домовленість — службова сторінка з маркером 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Один запит, усі секції та блоки. Вибір за замовчуванням.
Меню чи sitemapGET /v1/pagesУсі сторінки з href і SEO-параметрами, без контенту.
Одна секція — скажімо, промосмуга на кількох сторінкахGET /v1/sections/:markerМенша відповідь, коли решта сторінки береться деінде.
Одне значення — телефон, банер, цінаGET /v1/blocks/:marker?section=…Рівно один блок. Передавайте section, щоб бути точними.
Список дописів блогу чи один дописGET /v1/blog, /v1/blog/:slugДописи живуть поза деревом сторінок і мають пагінацію.
Мови сайтуGET /v1/langsПеремикачі мов, теги hreflang.

Що далі