# TopSysOps - сайт + админ-панель Продакшн-версия сайта top-sysops.ru: публичная часть с серверным рендерингом на Next.js (Главная, Акции, Услуги, Кейсы, FAQ, Блог с рич-текст редактором, Отзывы, Контакты), личный кабинет для клиентов (переписка с командой, ИИ-чат, профиль) и админ-панель (дашборд посещаемости, модерация отзывов, редактор прайс-листа, редактор акций, редактор кейсов и FAQ, редактор блога, переписка с клиентами, ИИ-ассистент для сотрудников, виджет чата поддержки на сайте). SEO: sitemap.xml, robots.txt и разметка schema.org генерируются самим Next.js. ## Из чего состоит ``` ├── docker-compose.yml # четыре сервиса: db, backend, nextjs, web ├── .env.example # шаблон настроек, скопировать в .env ├── backend/ # Node.js/Express API + PostgreSQL ├── public-site/ # Next.js (SSR) - публичный сайт и блог └── admin-app/ # React (Vite) - админка и личный кабинет + Caddy ``` - **db** - PostgreSQL. Хранит услуги/цены, акции, отзывы, статьи блога, статистику визитов, клиентов и учётные записи администраторов. - **backend** - REST API. При первом запуске сам накатывает миграции и создаёт администратора и стартовый прайс-лист/акции (те же позиции, что были на старом сайте). - **nextjs** - публичный сайт и блог, отдаются с сервера (SSR) - это и есть ответ на "нужен SEO": краулер получает готовый HTML с разметкой schema.org, а не пустую страницу, которую дорисовывает JavaScript. Не имеет отдельного порта наружу - доступен только через Caddy. - **web** - Caddy: собирает и раздаёт статику `admin-app` (админка и личный кабинет - им SEO не нужен, поэтому остались обычным SPA), проксирует `/api/*` на backend, всё остальное - на `nextjs`. Caddy же **сам получает и продлевает HTTPS-сертификат** Let's Encrypt - вручную ничего настраивать не нужно, только домен в `.env`. Почему не переписали админку и кабинет на Next.js тоже: SEO им не нужно вообще (они закрыты в `robots.txt` и требуют авторизации), а полный переезд уже готовой и работающей админки ради нулевой выгоды - это только риск что-то сломать. SSR применён именно там, где от него есть польза - на страницах, которые должны индексироваться. ## Требования - Сервер на Ubuntu 24.04 (2 ГБ RAM хватит с запасом). - Домен, у которого A-запись указывает на IP сервера - нужен для автоматического HTTPS. Если домена пока нет, можно временно развернуть по IP без HTTPS (см. `.env.example`). - Открытые порты 80 и 443. ## 1. Установка Docker на Ubuntu 24 ```bash sudo apt update sudo apt install -y ca-certificates curl gnupg sudo install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg sudo chmod a+r /etc/apt/keyrings/docker.gpg echo \ "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \ $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \ sudo tee /etc/apt/sources.list.d/docker.list > /dev/null sudo apt update sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin # чтобы не писать sudo перед каждой командой docker (перелогиньтесь после этого) sudo usermod -aG docker $USER ``` Проверка: `docker compose version`. ## 2. Загрузка проекта на сервер Скопируйте всю папку проекта на сервер, например через `scp` или `git`: ```bash scp -r topsysops-app your-user@your-server-ip:~/topsysops-app # или, если проект в git-репозитории: git clone <ваш-репозиторий> topsysops-app ``` ## 3. Настройка .env ```bash cd topsysops-app cp .env.example .env nano .env ``` Обязательно замените: - `POSTGRES_PASSWORD` - пароль базы данных; - `JWT_SECRET` - длинная случайная строка (`openssl rand -hex 32`); - `ADMIN_USERNAME` / `ADMIN_PASSWORD` - логин и пароль первого администратора; - `DOMAIN` - ваш домен (например `top-sysops.ru`) или `:80`, если тестируете по IP без HTTPS. Файл `.env` содержит секреты - он уже добавлен в `.gitignore`, не публикуйте его. ## 4. Запуск ```bash docker compose up -d --build ``` Первый запуск соберёт образы (нужен интернет на сервере), поднимет базу, применит миграции, создаст администратора и стартовый прайс-лист/акции, затем запустит сайт. Проверить статус и логи: ```bash docker compose ps docker compose logs -f backend ``` Дождитесь строки `[start] backend listening on port 4000`, затем откройте сайт в браузере по вашему домену (или `http://IP-сервера`, если `DOMAIN=:80`). Если используете реальный домен, Caddy автоматически выпустит сертификат при первом обращении по HTTPS - на это может уйти несколько секунд. ## Если TLS у вас на Nginx Proxy Manager Именно этот сценарий - в `.env.example` он уже настроен по умолчанию: `DOMAIN=http://top-sysops.ru`, `HTTP_PORT=7778`, порт 443 у контейнера закомментирован в `docker-compose.yml`. Со стороны NPM: 1. **Hosts -> Proxy Hosts -> Add Proxy Host**. 2. **Domain Names**: ваш домен (`top-sysops.ru`). 3. **Scheme**: `http`, **Forward Hostname/IP**: IP или hostname сервера, где запущен docker compose (`127.0.0.1`, если NPM на той же машине; иначе реальный IP), **Forward Port**: `7778` (или то, что указали в `HTTP_PORT`). 4. Вкладка **SSL**: выберите/запросите сертификат Let's Encrypt, включите **Force SSL** - весь HTTPS полностью на стороне NPM, контейнеру об этом вообще думать не нужно. 5. Save. Проверьте, что `docker compose ps` показывает `web` со проброшенным портом `7778->80` - именно на него должен смотреть Forward Port в NPM. Если после этого видите "too many redirects" - смотрите раздел "Устранение неполадок" ниже, обычно это означает, что `DOMAIN` в `.env` задан без схемы `http://`. ## 5. Первый вход в админку Откройте `https://ваш-домен/admin`, войдите под `ADMIN_USERNAME` / `ADMIN_PASSWORD` из `.env`, затем сразу зайдите в **Настройки -> Сменить пароль** и задайте собственный пароль - значение из `.env` дальше не используется системой, но лучше не оставлять его действующим. Из админки доступно: - **Дашборд** - визиты (всего/сегодня/график за 7 дней), количество отзывов; - **Отзывы** - одобрить / отклонить / удалить; - **Клиенты** - список зарегистрированных клиентов и переписка с ними (ответ на сообщения из личного кабинета, метка «ждёт ответа» для непрочитанных); - **Прайс-лист** - добавление и редактирование категорий и позиций услуг, скрытие позиции с сайта без удаления (иконка глаза); - **Акции** - то же самое для акций; - **Кейсы** - карточки портфолио (название, тег, статус с цветным индикатором, описание), с возможностью скрыть без удаления; - **FAQ** - вопросы и ответы для сайта; - **Блог** - статьи (заголовок, слаг, краткое описание, текст в WYSIWYG-редакторе с форматированием - заголовки, списки, ссылки, цитаты - публикация) - попадают на по-настоящему серверно отрендеренную страницу `/blog/` и в sitemap.xml сразу после сохранения; - **Контакты** - адрес, телефон, email, режим работы, ссылка на карту; - **ИИ-ассистент** - внутренний чат для сотрудников на базе OpenRouter (см. ниже); - **Настройки** - смена пароля и управление администраторами (добавить нового сотрудника с логином/паролем, удалить лишнего - кроме себя и последнего оставшегося администратора). Изменения в прайсе, акциях, кейсах и FAQ появляются на сайте в течение минуты: Next.js кэширует данные с публичного сайта на 60 секунд (`revalidate: 60`), это плата за серверный рендеринг - страницы отдаются мгновенно, а не после похода в базу на каждый заход. В самой админке изменения видны сразу же, без задержки. ## Обновление прайс-листа на уже развёрнутом сайте Стартовый прайс-лист (в `seed.js`) применяется только к пустой базе - то есть только при самом первом запуске. Для уже развёрнутого сайта обновление новой линейки услуг оформлено как обычные миграции (`backend/migrations/006_update_services_2026_07.sql` и `007_landing_tier.sql`) - они применяются **автоматически** при следующем запуске бэкенда, ничего вручную запускать не нужно: ```bash docker compose up -d --build ``` Система миграций (`backend/src/migrate.js`) гарантирует, что каждый файл выполнится ровно один раз - если уже разворачивали сайт раньше и обновляете код сейчас, эти миграции просто окажутся среди тех, что применятся при следующем перезапуске `backend`, наравне с остальными. Что делают эти миграции: убирают старую категорию "Разработка сайтов" (от 5 000 ₽) и добавляют: - **Разработка под ключ** - Лендинг (от 20 000 ₽, простая посадочная без бота/админки), MVP «Быстрый старт» (от 120 000 ₽, с формой-ботом и простой админкой), MVP Pro (от 450 000 ₽, с личным кабинетом и ИИ-ассистентом - как этот сайт), продакшн-разработка (от 1 200 000 ₽, индивидуально), Discovery-воркшоп (от 30 000 ₽), сопровождение проекта (от 40 000 ₽/мес); - **White-label и партнёрство** - white-label «Кабинет + ИИ-ассистент» (от 500 000 ₽), IT-мониторинг сайта (от 10 000 ₽/мес), реферальная и партнёрская программы. Цифры сверены с рынком Санкт-Петербурга (лендинги студийного уровня - 19 000-65 000 ₽, MVP с личным кабинетом у студий - от 260 000 ₽ и выше). Дальше все эти позиции редактируются как обычно - через админку («Прайс-лист»). Если после `docker compose up -d --build` старая категория всё равно видна - проверьте `docker compose logs backend | grep migrate`: там должны быть строки `applying 006_update_services_2026_07.sql` и `applying 007_landing_tier.sql`. Если их нет - значит контейнер `backend` не пересобрался/не перезапустился с новым кодом (проверьте `docker compose ps`, при необходимости `docker compose up -d --build backend` отдельно). ## SEO - **robots.txt** и **sitemap.xml** - генерируются самим Next.js (`public-site/app/robots.js` и `app/sitemap.js`), закрывают `/admin` и `/account` от индексации, доступны по адресам `https://ваш-домен/robots.txt` и `/sitemap.xml`. Sitemap включает главную, `/blog` и все опубликованные статьи - обновляется автоматически. - **schema.org (JSON-LD)** - рендерится прямо на сервере (это заслуга SSR: разметка оказывается в HTML, который получает краулер, а не дорисовывается JS уже в браузере). На сайте автоматически публикуется `LocalBusiness` (адрес, телефон, email из раздела «Контакты», рейтинг из одобренных отзывов), `FAQPage` (из раздела FAQ) и каталог услуг (`hasOfferCatalog`, из прайс-листа) - помогает Google/Яндексу показывать расширенные сниппеты (рейтинг со звёздами, раскрывающиеся вопросы в выдаче). На страницах статей блога - разметка `Article`. - **Блог с рич-текст редактором** - основной канал органического трафика для B2B IT-услуг. Пишите статьи через админку («Блог», обычный WYSIWYG-редактор: заголовки, списки, ссылки, цитаты) - каждая становится отдельной, по-настоящему серверно отрендеренной страницей на `/blog/` и сразу появляется в sitemap.xml. - **Серверный рендеринг (SSR)** - публичный сайт и блог теперь на Next.js: краулер и превью в соцсетях получают готовый HTML сразу, без ожидания JavaScript. Админка и личный кабинет остались SPA на Vite - им это не нужно, они и так закрыты в `robots.txt` и требуют входа. - Что дальше сделать руками (не автоматизируется из кода): зарегистрировать сайт в Яндекс.Вебмастере и Google Search Console (там же отправить sitemap.xml вручную первый раз), завести профиль в Яндекс.Картах/Google Business с тем же адресом и телефоном, что в разделе «Контакты» (важно для локального SEO по запросам вида «IT-аутсорсинг Санкт-Петербург»). ## Чат поддержки на сайте (TopTicket / Chatwoot) Виджет чата для посетителей сайта настраивается в `.env` и встраивается на этапе сборки Next.js-приложения (значения переменных `NEXT_PUBLIC_*` попадают в клиентский бандл, поэтому смена провайдера требует пересборки контейнера `nextjs`, а не `web`). По умолчанию уже включён TopTicket (ваша собственная разработка, help.top-sysops.ru) с реальными данными. Выберите вариант в `.env`: ```bash # TopTicket (по умолчанию) - embed-сниппет вашей разработки: #