Архитектура и границы приложений
Оглавление · Далее: сайт
Для чего нужен каждый компонент
| Компонент | Задача | Граница |
|---|---|---|
| Next.js | Server Components, страницы, tRPC, webhook, media proxy | Только опубликованный контент через CMS API |
| Mantine | Компоненты, тема и Styles API | Данные приходят через props |
| contracts/Zod | Типы и проверка HTTP данных | TypeScript не заменяет runtime validation |
| Strapi | Редактирование, Draft & Publish, REST и upload provider | Собственные npm/React 18/TS 5 |
| Garage | S3 API и public website bucket | Один узел, без AWS ACL и без HA |
| Hatchet Lite | События, очередь, выдача задач | Воркеры исполняют код отдельно |
| Node.js functions | Долгоживущий worker SDK | Нет HTTP server и scale-to-zero |
| PostgreSQL | Раздельные базы Strapi/Hatchet | Общий физический процесс |
| Caddy | TLS, reverse proxy | Не заменяет права CMS/API token |
Картина на одном VPS
Исходник схемы
flowchart LR Visitor[Посетитель] --> Caddy Caddy --> Web[Next.js] Caddy --> CMS[Strapi] Caddy -->|media origin| S3[Garage] Web -->|read-only / tagged cache| CMS Web -->|media proxy| S3 CMS -->|S3 upload| S3 CMS --> CMSDB[(strapi)] CMS -->|webhook| Web Producer[Отдельный producer] --> Hatchet[Hatchet Lite] Hatchet --> Worker[Node worker] Hatchet --> QueueDB[(hatchet)]
Две базы находятся в одном PostgreSQL; все контейнеры — на одном VPS. Сбой базы, CMS или хоста отражается на обновлении контента. Готовые страницы кеширует Next.js. Read-only token не передаётся браузеру; панель защищена собственным входом Strapi, а публичные изображения читаются без пароля. Draft media-файлы не становятся приватными автоматически.
Структура монорепозитория
apps/
web/ Next.js, Mantine, CMS adapter и серверные тесты
functions/ Долгоживущий Node.js-воркер
packages/
contracts/ Zod-схемы, типы и имя события
infra/
strapi/ Самостоятельный npm-проект CMS
postgres/ Первичное создание пользователей и баз
Caddyfile* Локальный и production reverse proxy
scripts/
setup-env.mjs Локальные секреты
hatchet-token.mjs
deploy/ Подготовка хоста и применение релизов
.github/workflows/ CI и деплой
docs/ Это руководство
В pnpm workspace входят только apps/* и packages/*. infra/strapi использует
свой package-lock.json, React 18 и TypeScript 5. Изоляция позволяет использовать
современные версии в сайте, не заставляя Strapi разделять несовместимые зависимости.
Зависимости пакетов
Исходник схемы
flowchart TD Contracts[contracts: Zod] --> Web[web: Next.js + tRPC + Mantine] Contracts --> Functions[functions: Hatchet SDK] Strapi[Strapi: отдельный npm lockfile]
Стрелки обозначают направление использования: web и functions импортируют contracts.
workspace:* означает локальный пакет, а не скачивание одноимённого пакета из npm.
pnpm -r build учитывает зависимости workspace: сначала собирается contracts.
Turborepo здесь не установлен; команды выполняет pnpm.
Два пути данных
Чтение: Strapi REST → серверный CMS client → Zod/contracts → repository → Server Component или tRPC → посетитель. Все страницы используют одну модель данных. Полная pagination читается до конца; ошибка второй страницы не выдаётся за полный список.
Обновление: Strapi webhook → HTTP handler → Zod → инвалидирование кеша Next.js. Страницы обновляются при следующем посещении без полной пересборки. Учебный Hatchet worker остаётся отдельным примером фоновой обработки.
Импорт: явный снимок публичного источника → нормализация HTML/дат → upload → Document Service create/publish. Архив нужен для аудита, не для runtime fallback.
Сборка, запуск и проверка типов
- Сборка превращает TypeScript воркера/contracts в JavaScript, а Next.js — в production-приложение со статически сгенерированными страницами и ISR.
- Запуск исполняет уже подготовленный код. В dev воркер запускает
.tsчерез Node 24. - Проверка типов ищет ошибки без выполнения бизнес-операций.
- Runtime-валидация проверяет данные, поступившие от внешнего источника.
TypeScript не защищает автоматически от неверного HTTP JSON: сетевой клиент может не использовать TypeScript вообще. Поэтому webhook проверяется Zod как в web, так и в воркере.
Принятые решения
Next.js выбран для страниц и серверного API. Vike в репозитории не используется.
Mantine выбран для интерфейса. Jest работает через next/jest, поэтому отдельный
Vite-проект для тестирования не нужен. Vitest и Playwright не установлены.
Hatchet даёт удобное объявление функций через SDK, очередь и панель выполнения. Процесс воркера при этом постоянно работает на нашем VPS: автоматического масштабирования, оплаты за вызов и отдельного контейнера для каждой функции нет.
Strapi собирается из стандартного проекта своим Dockerfile. Мы не полагаемся на неопределённый сторонний «готовый Strapi image».