diff --git a/app/layout.tsx b/app/layout.tsx index 3838fe63..37a45a6a 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -8,6 +8,7 @@ import { SITE_CONFIG } from '@/lib/metadata'; import { GoogleAnalytics } from '@next/third-parties/google'; import { Analytics } from '@vercel/analytics/react'; import Hotjar from '@/components/Hotjar'; +import Clarity from '@/components/Clarity'; import { ClientProviders } from '@/components/providers/ClientProviders'; const geistSans = localFont({ @@ -103,6 +104,7 @@ export default async function RootLayout({ {process.env.GA_MEASUREMENT_ID && } + ); diff --git a/components/Clarity.tsx b/components/Clarity.tsx new file mode 100644 index 00000000..e1ded1c2 --- /dev/null +++ b/components/Clarity.tsx @@ -0,0 +1,25 @@ +'use client'; + +import { useEffect, useRef } from 'react'; +import ClaritySDK from '@microsoft/clarity'; + +const clarityId = process.env.NEXT_PUBLIC_CLARITY_ID; +const isProduction = process.env.NODE_ENV === 'production'; + +/** + * Microsoft Clarity analytics component for session recordings, heatmaps, and user behavior. + */ +const Clarity = () => { + const isInitialized = useRef(false); + + useEffect(() => { + if (!isInitialized.current && isProduction && clarityId) { + ClaritySDK.init(clarityId); + isInitialized.current = true; + } + }, []); + + return null; +}; + +export default Clarity; diff --git a/package-lock.json b/package-lock.json index b636a9b1..a4f63b28 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "@headlessui/react": "^2.2.0", "@hocuspocus/provider": "^2.14.0", "@hookform/resolvers": "^4.1.0", + "@microsoft/clarity": "^1.0.2", "@next/third-parties": "^15.5.9", "@radix-ui/react-dropdown-menu": "^2.1.2", "@radix-ui/react-popover": "^1.1.2", @@ -3106,6 +3107,11 @@ "node": ">=10" } }, + "node_modules/@microsoft/clarity": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@microsoft/clarity/-/clarity-1.0.2.tgz", + "integrity": "sha512-9EZYROFpJxEGmQpHvUFqvD3ZJ7QQSqnibYSWmS+1xusoZfG1QQ1/Al9yVBBc11DWMbJrs1pe1hLT273it/skJg==" + }, "node_modules/@napi-rs/canvas": { "version": "0.1.88", "license": "MIT", diff --git a/package.json b/package.json index 04500781..f6283812 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "@headlessui/react": "^2.2.0", "@hocuspocus/provider": "^2.14.0", "@hookform/resolvers": "^4.1.0", + "@microsoft/clarity": "^1.0.2", "@next/third-parties": "^15.5.9", "@radix-ui/react-dropdown-menu": "^2.1.2", "@radix-ui/react-popover": "^1.1.2", diff --git a/services/analytics.service.ts b/services/analytics.service.ts index ebc0527e..4878926f 100644 --- a/services/analytics.service.ts +++ b/services/analytics.service.ts @@ -1,6 +1,7 @@ import { User } from '@/types/user'; import * as amplitude from '@amplitude/analytics-browser'; import { sendGAEvent } from '@next/third-parties/google'; +import ClaritySDK from '@microsoft/clarity'; export const LogEvent = { SIGNUP_PROMO_MODAL_OPENED: 'signup_promo_modal_opened', @@ -103,6 +104,11 @@ class AnalyticsService { identify.set(key, value); }); amplitude.identify(identify); + + const clarityUserId = userProperties.user_id || this.userId; + if (clarityUserId) { + ClaritySDK.identify(clarityUserId); + } } static async logEvent(