МЕРА B2C: Яндекс.Метрика и GA4 + открытие сайта для поисковой индексации #3436

Merged
lekss361 merged 1 commit from feat/mera-analytics-seo into main 2026-09-10 14:14:26 +00:00
Owner

Зачем

Статьи МЕРЫ опубликованы с UTM-метками, но посмотреть, приходил ли по ним кто-нибудь, было нечем: веб-аналитики на публичном контуре не было вовсе.

По ходу вскрылось, что второй половине запроса — «толкнуть в выдаче повыше» — мешает не оптимизация, а флаг: всё дерево mera-public отдавало robots: noindex, nofollow, а /robots.txt и /sitemap.xml возвращали 404. Флаг остался с этапа, когда домен был закрыт; в коде рядом стоял комментарий «снять вместе с открытием домена». Домен открыт с августа.

Что внутри

Счётчики

Метрика и GA4 монтируются только в mera-public/layout.tsx и никогда в корневом app/layout.tsx — иначе счётчик уехал бы в закрытый контур (/v2, /admin, /scrapers, /history), где анонимных посетителей нет, а приватные маршруты сотрудников есть.

Идентификаторы приходят build-time (NEXT_PUBLIC_YM_ID, NEXT_PUBLIC_GA_ID) — канон Dockerfile'а этого проекта. Пустое значение = тег не рендерится вовсе. Оба build-arg'а прописаны в ОБОИХ блоках CI, включая retry-сборку без кеша.

Вебвизор выключен намеренно. Он пишет ввод в поля, а на /estimate человек вводит адрес своей квартиры; раздел 9 политики этого не раскрывает. В коде рядом записано, что понадобится для включения: фраза в п. 9.1 + маскировка полей.

Цели воронки

Десять целей: cta_estimate, address_input_start, address_picked, free_result_ok|thin|none, calc_error, validation_error, suggest_failed, paid_teaser_shown.

Кнопок «Проверить квартиру» восемь штук в разных компонентах, все — обычные <a> через PublicLink. Вместо восьми копий onClick — один делегированный слушатель на document: девятая кнопка подключится сама.

Цель «оплата успешна» не заведена: вызова checkout во фронте нет вовсе, PAYMENTS_ENABLED выключен, страницы возврата не существует. Вешать пока не на что — вместо неё прокси-цель на показ платного тизера.

Индексация

Снят noindex, добавлены app/robots.ts и app/mera-public/sitemap.ts, metadataBase, canonical на короткие адреса, openGraph, JSON-LD Article на главной статье, места под коды подтверждения Вебмастера и Search Console.

robots.txt и sitemap.xml разведены по двум разным handle в Caddy не от хорошей жизни: у Next robots.txt — конвенция корня app/, а sitemap живёт в сегменте маршрута, и формы путей не совпадают.

152-ФЗ

Раздел 9 «Файлы cookie и веб-аналитика» в политике (обработчики названы поимённо — этого требует ч. 3 ст. 6) плюс уведомляющий, не блокирующий баннер с запоминанием в localStorage.

Гейт «названий площадок в публичной копии быть не должно» получил узкое исключение ровно на аналитические словосочетания в политике; голое «Яндекс» как площадка остаётся запрещённым и там.

Попутный баг: 301 со слэшем терял UTM

Замер на живом проде 10.09.2026:

https://meraocenka.ru/articles/?utm_source=vc
  → 301 → https://meraocenka.ru/articles      # query отброшен

Матчер @meraShortSlash собирал цель из regex-захвата пути и не переносил параметры. Код ответа оставался 301, поэтому смоук проблему не видел. Мессенджеры и автолинкификаторы дописывают слэш сами — атрибуция терялась именно на трафике по опубликованной ссылке. Починено тем же приёмом, что у соседних матчеров; в смоук добавлена проверка буквального Location.

Гейты

  • isPublicPath и периметр-тест узнали про новые машинные адреса
  • noindex-гейт развёрнут: падает, если флаг вернулся; расширен на строковую форму robots: "noindex"
  • новая проверка: корневых handle в site-блоке не появляется без объявления — раньше эту дверь гейт не видел вовсе

Проверено

  • tsc --noEmit и eslint --max-warnings=0 — чисто
  • npm run build проходит; /robots.txt и /mera-public/sitemap.xml отдаются по нужным адресам
  • при пустых ID в HTML нет ни одного обращения к mc.yandex.ru и googletagmanager
  • caddy validate — конфиг валиден
  • check-mera-public-isolation.mjs — изоляция от B2B не нарушена
  • полный vitest run зелёный, кроме двух тестов LoginPage, которые падают точно так же на нетронутом дереве (pre-existing, не из этого PR)

Не доведена локально контрольная сборка с подставленными тестовыми номерами счётчиков: машину занимали посторонние процессы, три попытки умерли не начав. Обратная проверка (пустые ID → внешних запросов ноль) сделана на полноценной сборке.

После мержа

Владельцу нужно завести счётчик в Яндекс.Метрике и ресурс в GA4, вписать номера в build-args в .forgejo/workflows/deploy-tradein.yml (места подготовлены и подписаны) и передеплоить. До этого момента счётчики не подключаются, а всё остальное — индексация, sitemap, cookie-баннер, починенный UTM — работает сразу.

Отдельно после деплоя: переобход в Вебмастере и Search Console, иначе снятый noindex будет висеть в кэше поисковиков неделями.

🤖 Generated with Claude Code

https://claude.ai/code/session_01CiUFZ3rmTNpp3DRajUo8KQ

## Зачем Статьи МЕРЫ опубликованы с UTM-метками, но посмотреть, приходил ли по ним кто-нибудь, было нечем: веб-аналитики на публичном контуре не было вовсе. По ходу вскрылось, что второй половине запроса — «толкнуть в выдаче повыше» — мешает не оптимизация, а флаг: **всё дерево `mera-public` отдавало `robots: noindex, nofollow`**, а `/robots.txt` и `/sitemap.xml` возвращали 404. Флаг остался с этапа, когда домен был закрыт; в коде рядом стоял комментарий «снять вместе с открытием домена». Домен открыт с августа. ## Что внутри ### Счётчики Метрика и GA4 монтируются **только** в `mera-public/layout.tsx` и никогда в корневом `app/layout.tsx` — иначе счётчик уехал бы в закрытый контур (`/v2`, `/admin`, `/scrapers`, `/history`), где анонимных посетителей нет, а приватные маршруты сотрудников есть. Идентификаторы приходят build-time (`NEXT_PUBLIC_YM_ID`, `NEXT_PUBLIC_GA_ID`) — канон Dockerfile'а этого проекта. **Пустое значение = тег не рендерится вовсе.** Оба build-arg'а прописаны в ОБОИХ блоках CI, включая retry-сборку без кеша. **Вебвизор выключен намеренно.** Он пишет ввод в поля, а на `/estimate` человек вводит адрес своей квартиры; раздел 9 политики этого не раскрывает. В коде рядом записано, что понадобится для включения: фраза в п. 9.1 + маскировка полей. ### Цели воронки Десять целей: `cta_estimate`, `address_input_start`, `address_picked`, `free_result_ok|thin|none`, `calc_error`, `validation_error`, `suggest_failed`, `paid_teaser_shown`. Кнопок «Проверить квартиру» восемь штук в разных компонентах, все — обычные `<a>` через `PublicLink`. Вместо восьми копий `onClick` — один делегированный слушатель на `document`: девятая кнопка подключится сама. Цель «оплата успешна» **не заведена**: вызова checkout во фронте нет вовсе, `PAYMENTS_ENABLED` выключен, страницы возврата не существует. Вешать пока не на что — вместо неё прокси-цель на показ платного тизера. ### Индексация Снят `noindex`, добавлены `app/robots.ts` и `app/mera-public/sitemap.ts`, `metadataBase`, canonical на **короткие** адреса, `openGraph`, JSON-LD `Article` на главной статье, места под коды подтверждения Вебмастера и Search Console. `robots.txt` и `sitemap.xml` разведены по двум разным `handle` в Caddy не от хорошей жизни: у Next `robots.txt` — конвенция корня `app/`, а sitemap живёт в сегменте маршрута, и формы путей не совпадают. ### 152-ФЗ Раздел 9 «Файлы cookie и веб-аналитика» в политике (обработчики названы поимённо — этого требует ч. 3 ст. 6) плюс уведомляющий, не блокирующий баннер с запоминанием в `localStorage`. Гейт «названий площадок в публичной копии быть не должно» получил **узкое** исключение ровно на аналитические словосочетания в политике; голое «Яндекс» как площадка остаётся запрещённым и там. ## Попутный баг: 301 со слэшем терял UTM Замер на живом проде 10.09.2026: ``` https://meraocenka.ru/articles/?utm_source=vc → 301 → https://meraocenka.ru/articles # query отброшен ``` Матчер `@meraShortSlash` собирал цель из regex-захвата пути и не переносил параметры. Код ответа оставался 301, поэтому смоук проблему не видел. Мессенджеры и автолинкификаторы дописывают слэш сами — атрибуция терялась именно на трафике по опубликованной ссылке. Починено тем же приёмом, что у соседних матчеров; в смоук добавлена проверка буквального `Location`. ## Гейты - `isPublicPath` и периметр-тест узнали про новые машинные адреса - noindex-гейт развёрнут: падает, если флаг **вернулся**; расширен на строковую форму `robots: "noindex"` - новая проверка: корневых `handle` в site-блоке не появляется без объявления — раньше эту дверь гейт не видел вовсе ## Проверено - `tsc --noEmit` и `eslint --max-warnings=0` — чисто - `npm run build` проходит; `/robots.txt` и `/mera-public/sitemap.xml` отдаются по нужным адресам - при пустых ID в HTML **нет ни одного** обращения к `mc.yandex.ru` и `googletagmanager` - `caddy validate` — конфиг валиден - `check-mera-public-isolation.mjs` — изоляция от B2B не нарушена - полный `vitest run` зелёный, кроме двух тестов `LoginPage`, которые падают **точно так же на нетронутом дереве** (pre-existing, не из этого PR) Не доведена локально контрольная сборка с подставленными тестовыми номерами счётчиков: машину занимали посторонние процессы, три попытки умерли не начав. Обратная проверка (пустые ID → внешних запросов ноль) сделана на полноценной сборке. ## После мержа Владельцу нужно завести счётчик в Яндекс.Метрике и ресурс в GA4, вписать номера в `build-args` в `.forgejo/workflows/deploy-tradein.yml` (места подготовлены и подписаны) и передеплоить. До этого момента счётчики не подключаются, а всё остальное — индексация, sitemap, cookie-баннер, починенный UTM — работает сразу. Отдельно после деплоя: переобход в Вебмастере и Search Console, иначе снятый `noindex` будет висеть в кэше поисковиков неделями. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01CiUFZ3rmTNpp3DRajUo8KQ
lekss361 added 1 commit 2026-09-10 13:54:28 +00:00
feat(mera): Метрика и GA4 на публичном контуре + открытие сайта для индексации
All checks were successful
CI Trade-In / changes (pull_request) Successful in 9s
CI / changes (pull_request) Successful in 9s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Successful in 1m19s
CI / openapi-codegen-check (pull_request) Successful in 2m6s
CI / backend-tests (pull_request) Successful in 17m40s
671fef758e
ЗАЧЕМ. Статьи МЕРЫ публикуются с UTM-метками, но посмотреть, приходил ли по
ним кто-нибудь, было физически нечем: веб-аналитики на публичном контуре не
было вовсе. Заодно вскрылось, что «толкнуть в выдаче» тоже нельзя — всё
дерево mera-public отдавало `robots: noindex, nofollow`.

СЧЁТЧИКИ. Яндекс.Метрика и GA4 подключаются ТОЛЬКО в `mera-public/layout.tsx`
и никогда в корневом `app/layout.tsx` — иначе счётчик уехал бы в закрытый
контур (/v2, /admin, /scrapers, /history), где анонимных посетителей нет, а
приватные маршруты сотрудников есть. Идентификаторы приходят build-time
(`NEXT_PUBLIC_YM_ID` / `NEXT_PUBLIC_GA_ID`) — канон Dockerfile'а этого
проекта: Next инлайнит NEXT_PUBLIC_* на сборке, runtime env их не подхватит.
Пустое значение = тег не рендерится вовсе, никаких `ym(undefined)`. Оба
build-arg'а прописаны в ОБОИХ блоках CI, включая retry-сборку без кеша.

Вебвизор выключен намеренно. Он пишет ввод в поля, а на `/estimate` человек
вводит адрес своей квартиры; раздел 9 политики этого не раскрывает. Включать
следует одним заходом с правкой политики и маскировкой полей — в коде рядом
записано, что именно понадобится.

ЦЕЛИ ВОРОНКИ. Десять целей: клик по CTA, начало ввода адреса, адрес выбран,
результат с разбивкой по вердикту (ok/thin/none), ошибка расчёта, ошибка
валидации, отказ подсказок, показ платного тизера. Кнопок «Проверить
квартиру» восемь штук в разных компонентах, все — обычные `<a>` через
PublicLink, поэтому вместо восьми копий onClick один делегированный
слушатель на document: девятая кнопка подключится сама. Цель «оплата
успешна» НЕ заведена — вызова checkout во фронте нет вовсе, PAYMENTS_ENABLED
выключен, страницы возврата не существует; вешать её пока не на что.

ИНДЕКСАЦИЯ. Снят noindex со всех публичных страниц, добавлены `app/robots.ts`
и `app/mera-public/sitemap.ts`, metadataBase, canonical на КОРОТКИЕ адреса,
openGraph и JSON-LD Article на главной статье. robots.txt и sitemap.xml
разведены по двум разным handle в Caddy не от хорошей жизни: у Next
robots.txt — конвенция корня app/, а sitemap живёт в сегменте маршрута, и
формы путей не совпадают.

152-ФЗ. Раздел 9 «Файлы cookie и веб-аналитика» в политике (обработчики
названы поимённо — этого требует ч. 3 ст. 6) + уведомляющий, не блокирующий
баннер. Гейт «названий площадок в публичной копии быть не должно» получил
узкое исключение ровно на аналитические словосочетания в политике; голое
«Яндекс» как площадка остаётся запрещённым и там.

ПОПУТНЫЙ БАГ (замер на живом проде 10.09.2026). `meraocenka.ru/articles/`
с UTM-метками отдавал 301 на адрес БЕЗ query — матчер @meraShortSlash
собирал цель из regex-захвата пути и терял параметры. Код ответа при этом
оставался 301, поэтому смоук проблему не видел. Мессенджеры и
автолинкификаторы дописывают слэш сами, то есть атрибуция терялась именно на
трафике по опубликованной ссылке. Починено тем же приёмом, что у соседних
матчеров; в смоук добавлена проверка буквального Location.

ГЕЙТЫ. `isPublicPath` и периметр-тест узнали про новые машинные адреса;
noindex-гейт развёрнут (падает, если флаг вернулся) и расширен на строковую
форму `robots: "noindex"`; заведена проверка, что корневых `handle` в
site-блоке не появляется без объявления — раньше эту дверь гейт не видел.

Проверено: tsc и eslint чисто, `npm run build` проходит, robots.txt и
sitemap.xml отдаются по нужным адресам, при пустых ID в HTML нет ни одного
обращения к mc.yandex.ru и googletagmanager, `caddy validate` валиден,
изоляция mera-public от B2B не нарушена. Два теста LoginPage падают и на
нетронутом дереве — не наши.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CiUFZ3rmTNpp3DRajUo8KQ
lekss361 merged commit aba07b581f into main 2026-09-10 14:14:26 +00:00
lekss361 deleted branch feat/mera-analytics-seo 2026-09-10 14:14:26 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: lekss361/gendesign#3436
No description provided.