diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 0b5aae3..db9d3b0 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -41,11 +41,14 @@ jobs: target: "/home/devops/deploy" - name: Execute commands uses: appleboy/ssh-action@v1.0.3 + env: + GA_MP_API_SECRET: ${{ secrets.GA_MP_API_SECRET }} with: host: ${{ secrets.IP }} username: devops key: ${{ secrets.SSH_KEY_PRIVATE }} port: ${{ secrets.PORT }} + envs: GA_MP_API_SECRET script_stop: true script: | cd ~/deploy diff --git a/README.md b/README.md index 033bb73..69f1d8c 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,16 @@ npm run serve:ssr:interviewer Opens at `http://localhost:4000` +### Runtime environment variables (SSR server) + +| Variable | Default | Purpose | +|---|---|---| +| `PORT` | `4000` | SSR server listening port | +| `GA_MP_API_SECRET` | _(unset)_ | GA4 Measurement Protocol API secret. When set, the SSR server sends a `visitor_hit` event for crawler / AI-agent requests so bot traffic shows in Google Analytics. Unset = no-op (dev/local). See [`docs/ga-ssr-visitor-type.md`](docs/ga-ssr-visitor-type.md). | +| `GA_MEASUREMENT_ID` | `G-6GS9Y7GJD3` | Optional override for the GA4 measurement ID used by the Measurement Protocol send. | + +In production the secret is stored as the `GA_MP_API_SECRET` GitHub Actions secret and forwarded into the deploy SSH session (`appleboy/ssh-action` `envs:` in `.github/workflows/deploy.yml`), where `docker-compose` substitutes it into `${GA_MP_API_SECRET}` in `docker-compose.frontend.yml`. It never enters the repo or image. + ## Docker ```sh diff --git a/docker-compose.frontend.yml b/docker-compose.frontend.yml index ba73976..2d714b9 100644 --- a/docker-compose.frontend.yml +++ b/docker-compose.frontend.yml @@ -14,5 +14,6 @@ services: - 8080:4000 environment: - NODE_ENV=production + - GA_MP_API_SECRET=${GA_MP_API_SECRET} networks: - interviewer-network diff --git a/docs/ga-ssr-visitor-type.md b/docs/ga-ssr-visitor-type.md new file mode 100644 index 0000000..f0711e0 --- /dev/null +++ b/docs/ga-ssr-visitor-type.md @@ -0,0 +1,133 @@ +# Сбор visitor_type в Google Analytics (SSR + Measurement Protocol) + +Инструкция по ручной настройке на стороне Google Analytics (GA4) для фичи, которая +различает посетителей сайта: человек / поисковый краулер / AI-агент и шлёт это в +аналитику. + +## Зачем это нужно + +Классификация посетителя происходит на **сервере** (Express SSR), а обычный GA4 +работает в **браузере** и срабатывает только когда исполняется JavaScript. Боты и +AI-агенты JS не исполняют, поэтому в браузерный GA они не попадают. Чтобы видеть их +статистику, сервер отправляет события напрямую через **GA4 Measurement Protocol**. + +Для этого нужно вручную сделать две вещи в интерфейсе GA4: + +1. получить секретный ключ `GA_MP_API_SECRET`; +2. зарегистрировать специальный параметр `visitor_type`. + +Названия пунктов меню даны на русском, в скобках — на английском. + +--- + +## Задача 1. Получить значение `GA_MP_API_SECRET` + +1. Зайти на **https://analytics.google.com** под своим аккаунтом. +2. Вверху слева убедиться, что выбран нужный **ресурс (property)** — тот, к которому + относится веб-поток с Measurement ID `G-6GS9Y7GJD3` (сайт techinterview.space). +3. Внизу левого меню нажать на шестерёнку **«Администратор» (Admin)**. +4. В столбце **«Ресурс» (Property)** найти раздел **«Сбор и изменение данных» + (Data collection and modification)** → нажать **«Потоки данных» (Data streams)**. +5. В списке нажать на свой **веб-поток (Web)** — там будет домен techinterview.space. +6. Прокрутить панель с деталями потока **вниз до конца** → пункт **«Секреты API + Measurement Protocol» (Measurement Protocol API secrets)**. Нажать на него. +7. Нажать **«Создать» (Create)**. +8. Ввести понятное название (nickname), например `ssr-server`. Нажать **«Создать»**. +9. Появится строка **«Секретное значение» (Secret value)** — длинный набор символов. + **Скопировать его.** Это и есть `GA_MP_API_SECRET`. + +> Это секрет. Не публиковать, не коммитить в гит. Куда вставить на сервере — см. раздел +> «Куда положить секрет на сервере» ниже. + +На той же панели вверху виден **Measurement ID** = `G-6GS9Y7GJD3` — это вторая часть, +она уже известна, отдельно ничего делать не нужно. + +--- + +## Задача 2. Настроить сбор `visitor_type` (специальный параметр) + +Без этого шага GA будет получать значение `visitor_type`, но **не покажет его в +отчётах**. + +1. В **«Администратор» (Admin)**, столбец **«Ресурс» (Property)**. +2. Раздел **«Отображение данных» (Data display)** → **«Специальные определения» + (Custom definitions)**. +3. Вкладка **«Специальные параметры» (Custom dimensions)**. +4. Кнопка **«Создать специальный параметр» (Create custom dimension)** справа вверху. +5. Заполнить поля: + + | Поле | Значение | + |---|---| + | **Название параметра** (Dimension name) | `visitor_type` | + | **Область действия** (Scope) | **«Событие» (Event)** | + | **Описание** (Description) | `human / search-crawler / ai-agent` | + | **Параметр события** (Event parameter) | `visitor_type` (точно так же, маленькими буквами) | + +6. Нажать **«Сохранить» (Save)**. + +> Поле **«Параметр события»** должно быть строго `visitor_type` — так же, как +> отправляет код. Опечатка или другой регистр — данные не свяжутся. + +--- + +## Важные нюансы + +- **Данные не собираются задним числом.** Параметр начнёт собираться только **после** + создания, поэтому создать его нужно до выкатки кода на прод. +- **В обычных отчётах появится через 24–48 часов** — это нормально. +- **Проверить сразу** можно в **«Отчёты» → «В реальном времени» (Realtime)** или в + **«Администратор» → DebugView** — там событие `visitor_hit` видно через пару минут. +- Само событие `visitor_hit` отдельно регистрировать не нужно — GA подхватывает новые + события автоматически. + +--- + +## Куда положить секрет на сервере + +Сервер читает ключ из переменной окружения `GA_MP_API_SECRET` во время работы +(runtime), а не из Angular `environment.ts` (те значения вшиваются в бандл на этапе +сборки и утекли бы в публичный код). + +Фронтенд на проде запускается через `docker-compose.frontend.yml` на Droplet +(DigitalOcean), а деплой идёт через GitHub Actions (`.github/workflows/deploy.yml`). +Секрет прокидывается через пайплайн: + +1. В GitHub репозитории добавить секрет: **Settings → Secrets and variables → + Actions → New repository secret** → имя `GA_MP_API_SECRET`, значение — из Задачи 1. + +2. В `docker-compose.frontend.yml` уже есть ссылка на переменную (без самого секрета): + + ```yaml + environment: + - NODE_ENV=production + - GA_MP_API_SECRET=${GA_MP_API_SECRET} + ``` + +3. В шаге деплоя (`appleboy/ssh-action`) секрет пробрасывается в SSH-сессию через + `envs`, и `docker-compose` подставляет его в `${GA_MP_API_SECRET}`: + + ```yaml + - name: Execute commands + uses: appleboy/ssh-action@v1.0.3 + env: + GA_MP_API_SECRET: ${{ secrets.GA_MP_API_SECRET }} + with: + # ... + envs: GA_MP_API_SECRET + ``` + +Так секрет хранится только в GitHub Secrets (рядом с остальными — `DO_TOKEN`, +`SSH_KEY_PRIVATE`) и не попадает в гит, образ или файл на сервере. Ротация: поменять +секрет в GitHub и передеплоить. + +**Локально:** если переменная не задана — код ничего не отправляет (no-op). Чтобы +проверить отправку локально: `GA_MP_API_SECRET=xxx npm run serve:ssr:interviewer`. + +--- + +## Как смотреть статистику + +Когда данные накопятся: **«Исследования» (Explore)** → отчёт «Свободная форма» +(Free-form) → параметр (dimension) **`visitor_type`**, показатель (metric) +**«Количество событий» (Event count)**, при желании фильтр по событию `visitor_hit`. +Получится сравнение: люди / поисковые краулеры / AI-агенты. diff --git a/docs/geo.md b/docs/geo.md index dfc2753..00ddbe4 100644 --- a/docs/geo.md +++ b/docs/geo.md @@ -242,6 +242,28 @@ The response also sets: Implementation: `src/server.ts` (the final `app.use(...)` SSR handler). The turndown instance strips `