Сайти

Сайт — це статика, яку ви завантажили: зібраний React, лендинг, документація. Вона віддається зі сховища на власній адресі, а не з домену застосунку.

Адреса сайту

У кожного сайту власний піддомен:

app.oxta.io                  застосунок
myapp.sites.oxta.io          ваш сайт

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

Оскільки піддомени різні, сайти ізольовані й один від одного: скрипт одного сайту не бачить сховища іншого.

Застосунок на цих адресах не відповідає взагалі: myapp.sites.oxta.io/api/…, /flows, будь-що — 404. Тож токен там не з'явиться навіть теоретично.

Раніше сайт віддавався ще й за адресою sites.oxta.io/myapp/. Так робити перестали: у тій формі всі сайти всіх користувачів мали одне походження, тобто скрипт одного сайту читав сховище іншого й міг звертатися до його /_api/… від імені відвідувача. Старі посилання не загубились — вони назавжди перенаправляються на піддомен (301, а для запитів із тілом 308, щоб метод не змінився).

Збірка

На піддомені сайт лежить у корені, тож звичайна збірка з абсолютними шляхами (/assets/app.js) працює як є — нічого налаштовувати не треба.

Під префіксом сайт лежить лише локально, за адресою /__site/myapp/ — піддоменів на localhost немає. Там ми підставляємо в HTML <base href="/__site/myapp/">, і відносні шляхи знаходяться; абсолютні <base> не виправляє. Тож якщо перевіряєте збірку локально, збирайте з відносним base: vite build --base=./ (або "homepage": "." у Create React App). На проді це не потрібно.

Адреса

Малі літери, цифри й одиничні дефіси, від 3 до 40 символів. Частина назв зайнята нами: api, app, www, help, sites і подібні.

Як публікувати

Натисніть «Опублікувати папку» й виберіть теку зі збіркою — браузер віддасть усі файли разом із відносними шляхами. Якщо все лежить в одній теці (dist/), ця тека обрізається, і dist/index.html стає index.html.

Повторна публікація того самого шляху перезаписує файл. Файли, яких у новій публікації немає, залишаються — щоб прибрати старе, видаліть сайт і створіть заново.

Межі: до 25 МБ на файл, до 100 МБ і 500 файлів на сайт.

Публікація сайту разом із флоу

Сайт і його API можуть їхати одним паком. Покладіть поруч зі збіркою теку _flows, у ній — по JSON-файлу на флоу, і натисніть «Опублікувати папку» як завжди:

dist/
  index.html
  assets/app.js
  _flows/
    get.index.json          → GET  /_api/
    post.orders.json        → POST /_api/orders
    orders/get.all.json     → GET  /_api/orders/*
    _nightly-report.json    → флоу без власного шляху

Завантаження розділяється надвоє: усе поза _flows публікується як файли сайту, а кожен .json усередині імпортується як флоу й чіпляється до шляху. Ніщо з _flows не стає файлом сайту — ні самі флоу, ні випадковий notes.txt, який туди поклали: про такий файл ми скажемо у відповіді, але не опублікуємо.

Що говорить назва файлу

Назва файлу і є маршрут:

  • <метод>.<назва>.json — метод один із get, post, put, patch, delete, any; без нього шлях відповідає на any;
  • теки вкладаються у шлях: _flows/admin/get.users.json стає GET /admin/users;
  • index означає саму теку: _flows/get.index.json — це GET /, а _flows/orders/get.index.jsonGET /orders;
  • all означає підстановку: _flows/orders/any.all.json — це ANY /orders/*;
  • назва, що починається з підкреслення (_nightly-report.json), або будь-що в теці з підкресленням (_flows/_lib/…), імпортується як звичайне флоу без шляху — туди кладуть флоу за розкладом чи підфлоу.

Флоу може сказати це й прямо — тоді назва файлу не має значення:

{ "name": "Замовлення", "route": { "method": "POST", "path": "/orders" }, "nodes": [] }

"route": false імпортує флоу без шляху.

Яку назву отримає флоу

Спереду додається назва сайту, тож флоу одного паку тримаються купи в списку: Замовлення із сайту shop стає Shop · Замовлення. Повторна публікація не наліплює префікс двічі.

Повторна публікація паку

Друга публікація оновлює те, що вже є, а не плодить копії:

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

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

Файл флоу, який не тримається купи, повертається у відповіді з поясненням, а решта паку все одно публікується.

Межі: до 50 флоу в одному паку і 50 шляхів на сайт.

Односторінкові застосунки

Перемикач «односторінковий застосунок» вирішує, що робити з невідомим шляхом:

  • увімкнено — шлях без розширення (наприклад /orders/42) віддає index.html, і маршрутизацію робить ваш скрипт;
  • вимкнено — такий шлях дає 404.

Відсутній файл із розширенням (/assets/app.js) завжди дає 404 — інакше зламаний шлях до скрипта тихо повертав би HTML.

Що віддається у заголовках

  • X-Robots-Tag: noindex, nofollow — сайти не потрапляють у пошук;
  • Cache-Control залежить від файлу: HTML перевіряється щоразу, файли з хешем у назві кешуються на рік;
  • ETag із R2, тож повторний запит отримує 304;
  • діапазони підтримуються, тож відео й аудіо перемотуються.

Власний API на тому самому походженні

Сайт може мати свій API: ви прив'язуєте шлях до флоу, і звернення до /_api/… запускає це флоу.

POST https://myapp.sites.oxta.io/_api/orders   → ваше флоу

Оскільки це те саме походження, що й сторінка, фронтенду не потрібен ні CORS, ні токен у коді.

  • шлях виглядає як /orders або /orders/*; точний шлях виграє в підстановки, довша підстановка — в коротшої;
  • метод можна вказати конкретний або ANY; конкретний виграє;
  • на відміну від вебхука, такий виклик чекає на відповідь типово (15 с), бо браузер її потребує. Щоб не чекати, надішліть Prefer: respond-async — прийде 202;
  • відповідь формує вузол «Відповідь» у флоу; без нього прийде {"ok": true};
  • флоу мусить бути вашим і увімкненим: чуже флоу прив'язати не вийде, вимкнене дає 503;
  • кожен виклик — це запуск флоу, тож він списує квоту runs. Ліміт частоти береться з налаштувань самого флоу або, якщо його нема, платформний дефолт 60 запитів на хвилину: понад ліміт — 429 із Retry-After.

Що бачить флоу у своєму тригері: headers, query, body, method і path (шлях уже без /_api).

Ці шляхи перехоплюються до підстановки односторінкового застосунку, тож невідомий /_api/… дає 404 JSON, а не index.html.

Як перевірити локально

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

http://localhost:8787/__site/myapp/
http://localhost:8787/__site/myapp/_api/orders

Кнопка «Відкрити» в картці сама веде на цю адресу, коли ви на localhost — у списку тоді видно позначку «відкривається локально».

Ця форма працює лише на localhost. На бойовому домені вона вимкнена: інакше HTML сайту виконувався б на походженні застосунку й міг би прочитати ваш токен.

Скільки сайтів входить у тариф

Ліміт — на кількість сайтів і на загальний обсяг файлів по всіх ваших сайтах:

ТарифСайтівОбсяг
Free125 МБ
Starter3100 МБ
Pro10500 МБ
Business502 ГБ
Scale20010 ГБ

Звернення до сайту не лімітуються — статика в рази дешевша за запуск флоу. А от кожен виклик /_api/… — це запуск флоу, тож він списує квоту runs.

На сторінці «Сайти» видно, скільки з тарифу вже зайнято. Понад ліміт: сайт не створиться, а публікація файлів відхилиться з назвою тарифу в повідомленні.

Вимикач

Перемикач «опубліковано» вимикає сайт миттєво: усі шляхи починають віддавати 404, файли лишаються на місці. Це те, чим варто скористатися, якщо на сайт хтось поскаржився.