diff --git a/next.config.ts b/next.config.ts index 3833d3d..a11f508 100644 --- a/next.config.ts +++ b/next.config.ts @@ -10,6 +10,7 @@ const nextConfig: NextConfig = { NEXT_PUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL, NEXT_PUBLIC_APP_URL: process.env.NEXT_PUBLIC_APP_URL, NEXT_PUBLIC_AUTH_REDIRECT_URL: process.env.NEXT_PUBLIC_AUTH_REDIRECT_URL, + NEXT_PUBLIC_YM_ID: process.env.NEXT_PUBLIC_YM_ID, }, images: { remotePatterns: [ diff --git a/src/app/[locale]/layout.tsx b/src/app/[locale]/layout.tsx index f9b67b4..fda543d 100644 --- a/src/app/[locale]/layout.tsx +++ b/src/app/[locale]/layout.tsx @@ -9,10 +9,11 @@ import { hasLocale, NextIntlClientProvider } from "next-intl"; import { getMessages } from "next-intl/server"; import clsx from "clsx"; -import YandexMetrika from "@/components/analytics/YandexMetrika"; +import { PageViewTracker,YandexMetrika } from "@/components/analytics"; import { loadChatsList } from "@/entities/chats/loaders"; import { loadUser, loadUserId } from "@/entities/user/loaders"; import { routing } from "@/i18n/routing"; +import { AnalyticsProvider } from "@/providers/analytics-provider"; import { AppUiStoreProvider } from "@/providers/app-ui-store-provider"; import { AudioProvider } from "@/providers/audio-provider"; import { ChatsInitializationProvider } from "@/providers/chats-initialization-provider"; @@ -68,24 +69,29 @@ export default async function RootLayout({ return ( + {/* Analytics Components */} - - - - - - - - - {children} - - - - - - - - + + + + + + + + + + + + {children} + + + + + + + + + ); diff --git a/src/components/analytics/PageViewTracker.tsx b/src/components/analytics/PageViewTracker.tsx new file mode 100644 index 0000000..d8770ef --- /dev/null +++ b/src/components/analytics/PageViewTracker.tsx @@ -0,0 +1,54 @@ +"use client"; + +import { useEffect } from "react"; +import { usePathname, useSearchParams } from "next/navigation"; + +/** + * Wait for Yandex Metrika to be loaded + * Retry mechanism with timeout to handle async script loading + */ +async function waitForYandexMetrika(maxAttempts = 10, delayMs = 100): Promise { + for (let i = 0; i < maxAttempts; i++) { + if (typeof window !== "undefined" && + typeof window.ym === "function" && + window.__YM_COUNTER_ID__) { + return true; + } + await new Promise(resolve => setTimeout(resolve, delayMs)); + } + return false; +} + +/** + * Page View Tracker Component + * + * Tracks page views in Yandex Metrika + * when route changes occur (client-side navigation). + * + * Must be included in the app layout or root component. + */ +export function PageViewTracker() { + const pathname = usePathname(); + const searchParams = useSearchParams(); + + useEffect(() => { + const url = pathname + (searchParams?.toString() ? `?${searchParams.toString()}` : ""); + + // Track page view in Yandex Metrika (with retry logic) + const trackYandexMetrika = async () => { + const isYmAvailable = await waitForYandexMetrika(); + + if (isYmAvailable && typeof window.ym === "function") { + const counterId = window.__YM_COUNTER_ID__; + if (counterId) { + window.ym(counterId, "hit", url); + } + } + }; + + // Execute YM tracking + trackYandexMetrika(); + }, [pathname, searchParams]); + + return null; +} diff --git a/src/components/analytics/YandexMetrika/YandexMetrika.tsx b/src/components/analytics/YandexMetrika/YandexMetrika.tsx index 0b4cdcb..0b54592 100644 --- a/src/components/analytics/YandexMetrika/YandexMetrika.tsx +++ b/src/components/analytics/YandexMetrika/YandexMetrika.tsx @@ -1,79 +1,52 @@ "use client"; -import { useEffect } from "react"; import Script from "next/script"; -const YANDEX_METRIKA_ID = 103412914; - -export default function YandexMetrika() { - useEffect(() => { - // Initialize Yandex.Metrika after script loads - const initializeYandexMetrika = () => { - if (typeof window.ym === "function") { - try { - window.ym(YANDEX_METRIKA_ID, "init", { - webvisor: true, - clickmap: true, - accurateTrackBounce: true, - trackLinks: true, - }); - } catch { - // Silently handle initialization errors - } - } - }; - - // Check if ym is already available or wait for it - if (typeof window.ym === "function") { - initializeYandexMetrika(); - } else { - // Wait for script to load - const checkYm = setInterval(() => { - if (typeof window.ym === "function") { - initializeYandexMetrika(); - clearInterval(checkYm); - } - }, 100); - - // Cleanup interval after 10 seconds - setTimeout(() => { - clearInterval(checkYm); - }, 10000); - - return () => clearInterval(checkYm); - } - }, []); +import { YANDEX_METRIKA_ID } from "@/shared/constants/analytics"; +/** + * Yandex Metrika Integration Component + * + * Loads Yandex Metrika tracking script dynamically using static counter ID. + * + * Initializes with: clickmap, trackLinks, accurateTrackBounce, webvisor. + * Page views are tracked by PageViewTracker component on route changes. + */ +export function YandexMetrika() { return ( <> - {/* Yandex.Metrika counter */}