К содержимому
Hub
All notes

August 22, 2026 · 2 min read

The counter was installed, the reports were empty: how CSP silently killed analytics

CSPАналитикаБезопасностьNext.js

The counter sat on the site for a week. Zero in the reports. Code in place, ID correct, flags set. It took three passes, and each found its own cause.

First: the content security policy. The analytics domain was missing from script-src, so the browser simply refused to load the script. The console said so plainly. Nobody read it, because the site worked.

Worth pausing here. Checking whether the counter works via typeof ym === "function" proves nothing: the stub function is created by the inline snippet before the external script loads. It is always there, even when there is nothing to load.

Second: the defer: true flag. It disables the automatic pageview — you are meant to send it manually. Nobody did. The flag looked like a performance nicety and was in fact an off switch.

Third, the least obvious: the session recorder ships its data over a WebSocket. For CSP, wss is a separate scheme, and permission for https does not cover it. Pageviews started arriving, session recordings did not. In the reports it looked exactly as if the recorder had never been enabled.

There is a fourth thing, unrelated to security. A Next site does not reload the page on link navigation, and the counter only counts the view it was started on. Without an explicit hit on route change, the whole dataset would be entry points only.

The general lesson: analytics is the one part of a site that stays equally silent whether everything is fine or everything is broken. Verify it by data actually delivered, not by code being present on the page.

TypeScript
const METRIKA = "https://mc.yandex.ru";const METRIKA_WS = "wss://mc.yandex.ru";const contentSecurityPolicy = [  "default-src 'self'",  `script-src 'self' 'unsafe-inline' 'unsafe-eval' ${METRIKA}`,  "style-src 'self' 'unsafe-inline'",  "img-src 'self' data: blob: https:",  // wss — отдельная схема: разрешение на https её не покрывает,  // и запись сессий блокировалась, пока просмотры шли нормально.  `connect-src 'self' ${METRIKA} ${METRIKA_WS}`,  "frame-ancestors 'none'",  "object-src 'none'",].join("; ");