Как устроен этот сайт

Витрина и приватная система живут на одном сервере, который я администрирую сам: ни хостинга сайтов, ни внешней панели, ни чужой базы. Ниже — что из чего собрано и какие развилки пришлось решать. Сайт сам себе главный экспонат, поэтому и разбор настоящий, с разменами, а не список технологий.

01 · Слои: от запроса до диска

  1. ГостьHTTPS

    Сертификат Let’s Encrypt выпускается и продлевается автоматически.

  2. Caddyобратный прокси

    Единственный контейнер, смотрящий наружу. Держит TLS, заголовки безопасности и CSP, сжимает ответы.

  3. Next.js 16приложение

    App Router, серверные компоненты. Один хост отдаёт витрину (apex), другой — приватную ОС: разводит их rewrite по домену, а не по кнопке.

  4. Слой доступа к даннымzod → repository → service

    Единственное место, где живут запросы к базе. Валидация стоит на границе, а не россыпью по обработчикам.

  5. PostgreSQLбаза

    Порт наружу не опубликован вовсе — база доступна только внутри docker-сети.

  6. Бэкапыpg_dump | gzip | gpg

    Одним конвейером, без открытого текста на диске. Наружу уезжает только зашифрованный файл.

02 · Решения и почему именно так

ADR-004

Защита в глубину: редирект — это не защита

Прокси, отправляющий гостя на форму входа, — удобство, а не граница безопасности. Настоящая проверка вызывается первой строкой в каждом серверном действии и при каждой загрузке данных. Ошибка в правилах прокси тогда стоит неудобства, а не утечки.

ADR-011

Секретность архитектурой, а не фильтром

Самые личные данные лежат в отдельных таблицах и никогда не проецируются в общий граф. Будь они одной таблицей с флагом «приватное», секретность держалась бы на правильности фильтра — а одна ошибка в условии означала бы утечку. Здесь фильтровать просто нечего. Отдельный тест следит, чтобы публичная зона не начала импортировать приватную: он краснеет — сборка не проходит.

ADR-010

Граф знаний — проекция, а не вторая база

Узлы графа собираются из данных, которые уже ведутся ежедневно, плюс ручные связи поверх. Автономный граф пришлось бы наполнять вторым вводом — и он неизбежно бы умер. Витрина показывает не весь граф, а курируемый снимок: черновик правится отдельно, публикация — отдельным действием, с возможностью отката.

ADR-008

Три слоя данных — и осознанная остановка

Валидация, репозиторий, сервис. При этом здесь намеренно нет агрегатов, event sourcing и CQRS: для однопользовательской системы это оверинжиниринг. Слоёв ровно столько, сколько окупается.

ADR-001

PostgreSQL, хотя SQLite было бы проще

Размен назван прямо: на своём сервере SQLite дешевле в обслуживании. Взят Postgres — ради конкурентного доступа нескольких модулей сразу и лёгкого пути на управляемую базу, если сервер придётся менять.

ADR-009

Хэш пароля в base64 — из-за одного символа

Сырой хэш содержит «$», а загрузчики переменных окружения — и в разработке, и в docker-compose — принимают его за подстановку и молча портят значение: вход ломался без единой ошибки в логах. Альтернативу (экранировать по-разному в двух местах) отвергли как ловушку. Хранение в base64 читается всеми одинаково.

03 · Стек

Витрина
React Three Fiber, собственные шейдеры, Lenis, офлайн-бейк мешей в бинарный формат
Приложение
Next.js 16, React 19, TypeScript, Tailwind CSS, Radix UI
Данные
PostgreSQL, Prisma, zod
Инфраструктура
Docker Compose, Caddy, Linux VPS
Качество
Vitest, ESLint, офлайн-проверка кадров витрины

04 · Исходники

Код приватной системы закрыт — в ней лежат мои личные данные. Всё остальное, включая учебные проекты и разборы, открыто.