Как устроена платформа
OnPress — сеть сайтов на общей платформе. Во второй версии вся работа идёт через API: вы не открываете редактор и не выбираете шаблон темы, вы присылаете markdown страницы и разметку фреймов, а платформа собирает из них сайт. Эта страница — модель целиком. Всё остальное в документации — подробности отдельных её частей.
Сущности
Сайт. Отдельный сайт сети со своим доменом, своими страницами, фреймами, наборами данных, стилями, меню и языками. Снаружи называется девятизначным идентификатором site_id (вида x9HTLHECq), который выдаётся при создании и никогда не меняется. Сайт — владелец всего остального: ничего из перечисленного ниже не делится между сайтами, фрейм или набор одного сайта на другом не существует.
Страница. Единица содержимого с адресом. Ее публикует запрос POST /pages/markdown: свойства страницы задают заголовок, слаг, адрес, язык, фрейм и SEO-поля, а текст API конвертирует в HTML-разметку. Страница — единственное, что открывается по адресу. Дерево адресов строится из родителей: /docs/v2/ru/guides/frames/ — это страница frejmy, у которой родитель v2, у того — docs.
Фрейм. Готовая разметка любого масштаба: оболочка статьи, шапка, подвал, блок FAQ, целый лендинг, выгруженный из конструктора. У фрейма есть только слаг по БЭМ (site__header, article, article__faq--simple) и сама разметка. Типа, адреса и человеческого имени у фрейма нет. Внутри разметки стоят блоки <!-- onpress/… --> (меню, содержимое страницы, повторитель списка, другой фрейм) и атрибуты data-op-bind, которые подставляют значения в атрибуты тегов.
Набор данных. Произвольный JSON-список с именем (faq, tariffs, team), который повторитель onpress/repeat во фрейме превращает в разметку. У набора может быть вариант на каждый язык: faq и рядом faq@ru.
Системный источник. Список, который платформа считает сама: пункты меню в областях темы (menu.header), настройки сайта (site.settings), языки (site.languages), главная (site.front_page), страницы (pages.all, pages.tree, pages.children…), хлебные крошки (breadcrumbs). Читается тем же повторителем, что и набор. Часть источников можно менять через то же имя — запись уходит туда, где эти данные живут на самом деле.
Стили. Два слоя. CSS-переменные темы (/data/css.variables) — закрытый список именованных значений: цвета, радиус, типографика восьми уровней, ширины. Собственный CSS сайта (/data/css) — произвольный лист, который подключается после темы и перебивает её.
Медиа. Файлы в медиатеке сайта. Загружаются по адресу или в base64, с автоматическими размерами и WebP-копиями, повторная загрузка того же файла не создаёт дубль.
Меню. Меню сайта, поставленные в области: header, footer_1, footer_2, footer_3. Для каждого языка своё меню в той же области.
Языки. Языки сайта. Язык есть у страницы, у варианта набора, у меню в области. Язык по умолчанию живёт без префикса в адресе, остальные — под /{slug}/.
Пользователь. Человек с учётной записью OnPress. На сайте он либо администратор этого сайта, либо никто: вы работаете со своими сайтами, а на чужой сайт API отвечает 404 site_not_found.
Как они связаны
сайт
├─ страница ──(_onpress_frame = id фрейма)──▶ фрейм
│ └─ тело: markdown → блоки; может содержать блоки onpress/…
├─ фрейм ──(frame/слаг)──▶ другой фрейм
│ ├─ onpress/content ─────────▶ тело страницы, которую собирают
│ ├─ onpress/repeat {"source"} ▶ набор данных или системный источник
│ ├─ frame/menu {"place"} ────▶ меню в области темы (с учетом языка)
│ └─ data-op-bind ───────▶ значения сайта, страницы, элемента списка
├─ наборы данных (+ варианты по языкам)
├─ стили: CSS-переменные + собственный CSS
├─ меню, языки, настройки, SEO-настройки
└─ медиатека
Связей ровно две, и обе явные. Страница называет один фрейм — мета страницы хранит его id, а задают фрейм свойство frame, PATCH /pages/{ref} или POST /frames/{ref}/assign. Фрейм называет другие фреймы по слагу вызовом <!-- frame/слаг /-->. Никаких умолчаний сайта, типов и «шаблона по месту» нет: если страница не назвала фрейм, у неё нет вида.
Отсюда главное следствие для работы: на сайте v2 страница без фрейма отвечает 404. Она опубликована, лежит в базе, читается через API, но по адресу посетитель получает страницу «не найдено» (её рисует страница сайта со слагом 404, собранная своим фреймом, если такая есть). Дайте странице фрейм — и адрес оживёт.
Как страница собирается по шаблону
Запрос посетителя к /docs/v2/:
- Платформа находит страницу по адресу и её язык.
- Тема читает фрейм страницы и собирает его по шаблону: разбирает разметку, выполняет каждый блок
onpress/…для этой страницы и этого языка, подставляет значенияdata-op-bind. - Блок
onpress/contentвыводит тело самой страницы — markdown, превращённый при публикации в блоки, с якорями у заголовков. Вызовframe/…выводит вложенный фрейм, собранный тем же способом в том же контексте.onpress/repeatберёт список и повторяет свой шаблон по разу на элемент. - Если фрейм — обычная оболочка (без
<!doctype>), тема оборачивает результат в документ со своим<head>: SEO-разметка, иконки, счётчики аналитики,hreflang, стили темы, CSS-переменные и собственный CSS. Если фрейм сам целый документ (<!doctype html>…), обвязка вставляется прямо в его<head>и<body>.
Сборка идёт из исходника фрейма на каждом запросе, поэтому правка меню, набора данных, языков или настроек видна на обычных страницах сразу, как только сброшен кэш страниц. Кэш страниц API сбрасывает сам после своих записей.
Статическая копия
У части страниц есть статическая копия: готовый HTML лежит файлом, и веб-сервер отдаёт его мимо PHP. Копия появляется, когда выполнено всё сразу: страница опубликована и без пароля, у неё есть фрейм, этот фрейм не зависит от страницы, и адрес ложится в дерево файлов (латиница без процентов, первый сегмент не зарезервирован платформой).
Фрейм зависит от страницы, если в нём есть хотя бы одно из: блоки onpress/content, onpress/pager, onpress/docs-nav, onpress/outline, onpress/share-ai, onpress/featured-image, onpress/terms, onpress/assistant, onpress/faq-schema; бинд с токеном page.*; onpress/repeat с match, limit или offset от page.*; повторитель по контекстному источнику (pages.children, pages.siblings, pages.adjacent, pages.translations, breadcrumbs). Такой фрейм помечается renders_on_request: true и собирается на каждом запросе; копии у его страниц не бывает, потому что одна копия показала бы на другой странице чужой текст. Правило действует транзитивно: оболочка, вложившая зависимый фрейм вызовом frame/…, тоже зависима.
Типичный расклад: оболочка статьи или документации с onpress/content — всегда на запросе; лендинг, выгруженный целиком из конструктора, где текст зашит в разметку, — статическая копия.
Как именно отдаётся страница, API говорит в каждом ответе публикации полем printed.how: static — есть копия, on_request — собирается на запросе, not_published — у страницы пока нет адреса (черновик, неопубликованный родитель), theme — у страницы нет фрейма (на сайте v2 это значит 404). Подробно — в сборке страницы и статической копии.
Пересборка
Копию надо обновлять, когда меняется то, из чего она собрана. API делает это сам: запись фрейма пересобирает фреймы, которые его вкладывают; запись меню — фреймы с меню; запись набора — фреймы, которые его читают. Результат приходит в ответе полем rebaked, вместе со сбросом кэша страниц. Если что-то поменяли мимо API, есть ручная пересборка POST /frames/rebake. Зависимым от страницы фреймам пересобирать нечего, но кэш их страниц сбрасывается всё равно.
Где что хранится
Знать это не обязательно, но полезно, когда что-то ведёт себя странно. Всё лежит в таблицах своего сайта: страницы — записи page с метой (фрейм — _onpress_frame, SEO — SEO-поля страницы, свои ключи из свойств страницы — метаполя); фреймы — реестр в опции onpress_frames и исходник каждого в onpress_frame_<id>; наборы — реестр onpress_datasets и onpress_dataset_<имя> (вариант языка — onpress_dataset_<имя>__<lang>); собственный CSS — onpress_custom_css; CSS-переменные — две опции темы плюс onpress_design_tokens. Сетевых опций для содержимого нет намеренно: всё, что относится к сайту, остаётся на сайте.
Два контура: API и исполнитель
Публичный контракт — сервис API v2 на Node.js: он проверяет токен и права, выбирает сайт, пишет страницы, фреймы, наборы и меню прямо в базу. Сборку по шаблону, миниатюры медиа, настройки языков и SEO через хуки сайта и выгрузку тела в markdown он поручает PHP-исполнителю на домене сайта через внутренний канал. Вам этот канал не виден и не нужен: всё делается через https://onpress.pro/api/v2/. Если исполнитель на сайте недоступен, ответ приходит с кодом internal_unavailable (502) — это не ошибка запроса.
Дальше
- Быстрый старт — всё это на практике за десять запросов.
- Страницы и фреймы — две опоры платформы.
- Сборка страницы и статическая копия — подробности про копии, языки и пересборку.