- Next 16.4 и Payload 3.91 в одном приложении, PostgreSQL с UUID, TypeScript 6, ESLint 9, Prettier, Vitest - Payload: администраторы с ролями, multi-tenant по сайтам, локализация en/ru/de/fr/es, интерфейс админки ru/en, языки сайта (sites.locales) - Коллекции sites, domains, currencies, countries, locations, tags, tariffs, payment-systems (manual), media и начальные данные (pnpm seed) - Proxy: хост → домен, язык и валюта, внутренние маршруты по хосту; /admin и /api закрыты на доменах брендов - Токены и темы брендов, оболочки сайта и кабинета, переключатели языка, валюты и темы, заглушки страниц - /svc/health, юнит systemd veilix-dev.service - Документация: решения D-022…D-024, команды в CLAUDE.md Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
13 KiB
13 KiB
02. Архитектура
1. Стек
| Что | Версия на 20.10.2026 | Комментарий |
|---|---|---|
| Node.js | 24 LTS (на сервере 24.22) | |
| pnpm | 12.x | ставится root через corepack enable pnpm; версия фиксируется полем packageManager |
| Next.js | 16.4.x | App Router, React Server Components |
| React | 19.x | |
| Payload CMS | 3.91.x | Payload 4 в бете; переход после стабильного релиза (D-002) |
| PostgreSQL | 18 (на хосте) | адаптер @payloadcms/db-postgres, idType: 'uuid' |
| Tailwind CSS | 4.x | токены из макета как CSS-переменные |
| TypeScript | 6.0 | не 7.0: typescript-eslint из eslint-config-next поддерживает только TS < 6.1 |
| ESLint | 9.39 | не 10: eslint-plugin-react, import и jsx-a11y не заявляют поддержку ESLint 10 |
| Vitest | 5.x | модульные тесты |
| next-intl | 4.x | переводы интерфейса, префиксы языков в URL |
| Nodemailer | через @payloadcms/email-nodemailer |
транспорты SMTP, sendmail, «только журнал» |
| ALTCHA | 3.x | капча без третьих сторон |
| grammY | 1.x | Telegram-боты, этап 3 |
| Stripe SDK | актуальный | этап 4 |
Точные версии закреплены в package.json: на этапе 1a — Next 16.4.2, Payload 3.91.0, React 19.3.1, next-intl 4.14.9, Tailwind 4.3.3, pnpm 12.14. На старте 1a Payload 4 был в бете (4.0.0-beta.3), поэтому начали на 3.91 (D-002). Телеметрия Next.js и Payload выключена.
2. Одно приложение на все домены
- Одно Next.js-приложение, Payload установлен внутрь как библиотека: админка, API и фронтенды — один процесс (D-001).
- Proxy Next.js (
src/proxy.ts, бывший middleware) по заголовку Host находит домен и узнаёт сайт (dedicatedилиmass) и тип фронтенда: сайт, кабинет или MiniApp. Список активных доменов proxy берёт у/svc/internal/domains(по токену из.env) и держит в памяти 15 секунд: правка домена в админке применяется в пределах 15 секунд (D-022). - Proxy переписывает запрос во внутренний маршрут по хосту:
/m/{хост}/{язык}/{валюта}/…— сайт,/c/{хост}/{язык}/…— кабинет,/t/{хост}/…— MiniApp. Снаружи эти пути недоступны: прямой запрос к ним получает ещё один префикс и попадает в 404. Решения proxy — чистая функцияsrc/core/routing/decide.tsс модульными тестами. - Хост админки (
plc.veilix.cc,dev.plc.veilix.cc) задаётся в.env. Только на нём работают/adminи REST API Payload. На доменах брендов эти пути закрыты. - Сайтам, кабинетам и MiniApp хост админки не нужен:
a. данные Payload они читают в том же процессе через Local API — это вызов функции, а не HTTP-запрос;
b. запросы браузера (формы, вход, действия в кабинете) идут на тот домен, где открыта страница: server actions и эндпоинты
/svc/…; c. вебхуки платёжных систем и Telegram приходят на публичные домены, не на хост админки; d. файлы медиатеки отдаются через домены сайтов. - Ядро общее (биллинг, ключи, вход, рефералы, почта), UI-кит общий, а страницы у каждого бренда свои: данные в дизайнах представлены по-разному.
3. Структура кода
src/
proxy.ts — хост → домен, язык, валюта, внутренний маршрут
payload.config.ts
payload-types.ts — типы Payload (pnpm generate:types)
app/
(payload)/ — админка и REST API Payload (стандартные файлы)
(front)/m/[host]/[locale]/[currency]/(site)/… — маркетинговые сайты
(front)/c/[host]/[locale]/(shell)/… — кабинет с навигацией
(front)/c/[host]/[locale]/(plain)/… — вход и оформление заказа
(front)/t/[host]/… — MiniApp (этап 3)
(front)/context.ts — домен, язык и валюта страницы, данные оболочек
svc/… — свои эндпоинты: health, internal/domains; дальше ALTCHA, вебхуки
payload/
collections/ access/ fields/ hooks/ components/
core/ — бизнес-логика без привязки к UI
sites.ts domains/ routing/ currencies/ payments/ — этап 1a
billing/ keys/ auth/ referral/ email/ geo/ — следующие этапы
brands/
dedicated/ mass/ — различия брендов; страницы и компоненты — с этапа 1b
ui/ — общие компоненты: оболочки сайта и кабинета, переключатели, тема, логотип, иконки
i18n/ — языки, конфиг next-intl и messages/{en,ru,de,fr,es}.json
styles/ — Tailwind, токены брендов
seed/ — начальные данные (pnpm seed)
ops/systemd/ — юниты systemd
scripts/ — служебные скрипты (очистка dev-базы)
Корневые layout сайта и кабинета дают только документ: <html> с lang, data-site и скриптом темы. Шапка, подвал и навигация — во вложенных группах (site), (shell) и (plain).
Бизнес-логика живёт в core/ и вызывается одинаково из хуков Payload, server actions, вебхуков, фоновых задач и (на этапе 3) бота.
4. Окружения
| dev | prod | |
|---|---|---|
| Пользователь ОС | vx_dev (под ним работают редактор и сессии Claude) | vx_prod (без редактора и ИИ-агентов) |
| Папка | /home/vx_dev/veilix_dev — рабочая копия git |
/home/vx_prod/veilix — releases/, симлинк current, shared/ |
| Запуск | pnpm dev (горячая перезагрузка) |
pnpm start из current после pnpm build |
| Порт | 3100 | 3000 |
| База | veilix_dev |
veilix_prod |
| Схема базы | синхронизируется автоматически (push); если push задаёт вопросы — pnpm db:reset и pnpm seed |
миграции Payload, применяются при выкладке |
| Домены | dev.*, dev.plc.veilix.cc |
основные, plc.veilix.cc |
| Секреты | /home/vx_dev/veilix_dev/.env |
/home/vx_prod/veilix/shared/.env |
| Файлы (media) | /home/vx_dev/veilix_dev/media |
/home/vx_prod/veilix/shared/media |
- Без Docker: оба окружения — обычные процессы Node, каждый под своим пользователем ОС, запускаются одинаково — systemd user-юнитами:
veilix-dev.serviceу vx_dev,veilix.serviceу vx_prod. У vx_dev нет доступа к файлам, секретам, логам и базе prod. Подробно — D-003, D-004. - Dev закрыт от посторонних в nginx и запрещён к индексации, см. 09-infrastructure.md.
- Платёжные системы в dev — только в тестовом режиме.
5. Выкладка в prod
- В dev: изменения схемы оформляются миграцией (
payload migrate:create), коммит, push в Gitea, тег релиза. До первой выкладки (этап 1h) миграции не накапливаем: перед ней создаём одну начальную по итоговой схеме. Механизм проверен на этапе 1a: миграция, применённая к пустой схеме, создаёт те же таблицы, что push в dev. - Выкладка — команда
deploy <тег>пользователя vx_prod, появится на этапе 1h. Запускает её пользователь; по желанию — sudo-правило, разрешающее vx_dev запускать только эту команду (Q-18). Шаги: a. взять тег из Gitea (у vx_prod ключ только на чтение) в новую папкуreleases/<дата-тег>; b.pnpm install --frozen-lockfileиpnpm build; ссылки наshared/.envиshared/media; c. применить миграции кveilix_prod; d. переключить симлинкcurrentна новый релиз и перезапуститьveilix.service; e. проверить/svc/health; хранить 5 последних релизов. - Откат: переключить
currentна прошлый релиз и перезапустить. Миграции пишем так, чтобы прошлый релиз продолжал работать с новой схемой (сначала добавляем, удаляем позже). - Простой при выкладке — только время перезапуска процесса, несколько секунд.
6. Рендеринг и кэширование
- Маркетинговые страницы собираются статически в вариантах «домен × язык × валюта» при первом запросе (ISR) и кэшируются. Proxy выбирает вариант по префиксу языка и cookie валюты.
- Изменение в админке (цена, статус локации, FAQ) вызывает хук Payload, и Next.js пересобирает затронутые страницы за секунды (on-demand revalidation по тегам). Запасной вариант — плановое обновление раз в 5 минут.
- При оформлении заказа наличие и цена проверяются заново на сервере. Устаревшая страница не может продать то, чего нет.
- Кабинет рендерится на сервере (SSR) для каждого запроса, без кэша.
- Маркетинговые домены не ставят cookies, кроме выбора языка, валюты и темы.
7. Фоновые задачи
Встроенная очередь Payload (Jobs Queue) с расписанием cron. Исполнитель работает внутри процесса приложения (autoRun), в dev его можно отключить переменной окружения.
| Задача | Расписание | Что делает |
|---|---|---|
renewals |
каждые 15 минут | продление ключей, у которых срок подходит (см. 04-billing.md) |
reminders |
каждый час | напоминания за 3 дня до окончания |
expiry |
каждые 15 минут | перевод в льготный период и в «истёк» |
send-email |
по событию | отправка письма с повторами при ошибке |
cleanup |
раз в сутки | старые коды входа, сессии, записи журнала писем |
reconcile |
раз в сутки | сверка кэша баланса с суммой движений по счёту |