Microsoft Clarity

Consent-gated behavioral analytics with heatmaps, session recordings, and SPA route tracking — all on the free tier.

Why Microsoft Clarity?

Google Analytics tells us how many people visit and which pages they view, but it cannot show how they interact with those pages. Microsoft Clarity fills this gap with two features that are otherwise expensive or absent:

  • Heatmaps — aggregated click, scroll, and movement maps for every page, revealing which sections attract attention and which are skipped.
  • Session recordings — anonymized replays of real visitor sessions, showing navigation paths, confusion points, and rage clicks.

Clarity is completely free with no traffic caps, making it ideal for a nonprofit project. It runs alongside Google Analytics without conflict, each serving a distinct purpose: quantitative metrics (GA4) versus qualitative behavior (Clarity).

Consent-Gated Loading

Clarity is not loaded via Google Tag Manager. Instead, the cookie consent component dynamically injects the Clarity tracking script into <head> only after the visitor explicitly accepts Analytics cookies. This design ensures GDPR compliance by default.

How Loading Works

StepAction
1Visitor arrives — no Clarity script is present on the page
2Cookie consent banner appears, offering Analytics and Marketing toggle categories
3Visitor accepts Analytics → loadMicrosoftClarity() is called
4An inline <script> is appended to <head> that bootstraps the Clarity SDK using the project ID from NEXT_PUBLIC_CLARITY_PROJECT_ID
5Clarity starts recording and sending data to the dashboard

If the visitor later revokes Analytics consent, the component actively deletes Clarity’s first-party cookies (_clck and _clsk) and the script is not reloaded until consent is granted again.

Cookie Categories

The consent UI groups third-party services into two categories. Clarity falls under Analytics, alongside Google Analytics:

CategoryServicesCookies
AnalyticsGoogle Analytics, Microsoft Clarity_ga, _gid, _clck, _clsk
MarketingMeta Pixel_fbp, fr

SPA Route Tracking

A static site exported from Next.js App Router behaves as a single-page application after the initial load — navigating between pages uses client-side routing, which means Clarity would record every page view as the landing page URL. To solve this, a dedicated ClarityRouteTracker component is rendered in the root layout.

How It Works

  • The component uses usePathname() from next/navigation to listen for route changes.
  • On each navigation, it calls window.clarity('set', 'page', path) to tell Clarity which page the visitor is now viewing.
  • It prepends NEXT_PUBLIC_BASE_PATH to the pathname so the paths recorded in Clarity match the deployed URLs (important for GitHub Pages, which uses a repository-name prefix).
  • If window.clarity does not exist (visitor declined consent), the component silently does nothing.

Render Position

ClarityRouteTracker renders before the main content in the root layout, ensuring it intercepts every navigation regardless of how deeply a component tree updates:

<body> <GoogleTagManagerNoScript /> <ClarityRouteTracker /> ← route tracking <Header /> {children} <Footer /> <CookieConsent /> ← injects Clarity script </body>

Testing

The ClarityRouteTracker has a full Jest test suite that covers seven scenarios:

  • Basic rendering — the component returns null (no DOM output)
  • Route call — verifies window.clarity('set', 'page', '/some-path') is invoked with the current pathname
  • Base path handling — when NEXT_PUBLIC_BASE_PATH is set, the path is prefixed correctly
  • Root path — ensures / does not produce a double-slash
  • Graceful degradation — no error is thrown when window.clarity is undefined (consent not granted)
  • Route updates — re-rendering with a new pathname triggers a new Clarity call
  • No double slashes — edge-case test for basePath + root pathname

Environment Variables

VariablePurposeDefault
NEXT_PUBLIC_CLARITY_PROJECT_IDClarity project identifier, injected into the tracking scriptXXXXXXXXXX
NEXT_PUBLIC_BASE_PATHPrepended to pathnames sent to Clarity so recorded pages match deployed URLs'' (empty)

Lessons Learned

  • SPAs need manual route tracking. Without ClarityRouteTracker, every session shows the landing page for every page view. This is easy to miss because the behavior only becomes obvious after analyzing real sessions in the Clarity dashboard.
  • GTM is not always the right loader. Loading Clarity through GTM would add complexity (container tag configuration, consent mode mapping) without benefit. A single createElement('script') call with dynamic consent gating is simpler and more transparent.
  • Cookie cleanup matters. Deleting _clck and _clsk when consent is withdrawn demonstrates genuine GDPR respect rather than merely stopping the script.
  • Free tier, no compromises. Clarity has no traffic limits, no feature restrictions, and no paid upgrade required. For a nonprofit research project, this is invaluable.

Related