Как устроен этот сайт
Витрина и приватная система живут на одном сервере, который я администрирую сам: ни хостинга сайтов, ни внешней панели, ни чужой базы. Ниже — что из чего собрано и какие развилки пришлось решать. Сайт сам себе главный экспонат, поэтому и разбор настоящий, с разменами, а не список технологий.
01 · Слои: от запроса до диска
- ГостьHTTPS
Сертификат Let’s Encrypt выпускается и продлевается автоматически.
- Caddyобратный прокси
Единственный контейнер, смотрящий наружу. Держит TLS, заголовки безопасности и CSP, сжимает ответы.
- Next.js 16приложение
App Router, серверные компоненты. Один хост отдаёт витрину (apex), другой — приватную ОС: разводит их rewrite по домену, а не по кнопке.
- Слой доступа к даннымzod → repository → service
Единственное место, где живут запросы к базе. Валидация стоит на границе, а не россыпью по обработчикам.
- PostgreSQLбаза
Порт наружу не опубликован вовсе — база доступна только внутри docker-сети.
- Бэкапыpg_dump | gzip | gpg
Одним конвейером, без открытого текста на диске. Наружу уезжает только зашифрованный файл.
02 · Решения и почему именно так
Защита в глубину: редирект — это не защита
Прокси, отправляющий гостя на форму входа, — удобство, а не граница безопасности. Настоящая проверка вызывается первой строкой в каждом серверном действии и при каждой загрузке данных. Ошибка в правилах прокси тогда стоит неудобства, а не утечки.
Секретность архитектурой, а не фильтром
Самые личные данные лежат в отдельных таблицах и никогда не проецируются в общий граф. Будь они одной таблицей с флагом «приватное», секретность держалась бы на правильности фильтра — а одна ошибка в условии означала бы утечку. Здесь фильтровать просто нечего. Отдельный тест следит, чтобы публичная зона не начала импортировать приватную: он краснеет — сборка не проходит.
Граф знаний — проекция, а не вторая база
Узлы графа собираются из данных, которые уже ведутся ежедневно, плюс ручные связи поверх. Автономный граф пришлось бы наполнять вторым вводом — и он неизбежно бы умер. Витрина показывает не весь граф, а курируемый снимок: черновик правится отдельно, публикация — отдельным действием, с возможностью отката.
Три слоя данных — и осознанная остановка
Валидация, репозиторий, сервис. При этом здесь намеренно нет агрегатов, event sourcing и CQRS: для однопользовательской системы это оверинжиниринг. Слоёв ровно столько, сколько окупается.
PostgreSQL, хотя SQLite было бы проще
Размен назван прямо: на своём сервере SQLite дешевле в обслуживании. Взят Postgres — ради конкурентного доступа нескольких модулей сразу и лёгкого пути на управляемую базу, если сервер придётся менять.
Хэш пароля в 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 · Исходники
Код приватной системы закрыт — в ней лежат мои личные данные. Всё остальное, включая учебные проекты и разборы, открыто.