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
| Step | Action |
|---|---|
| 1 | Visitor arrives — no Clarity script is present on the page |
| 2 | Cookie consent banner appears, offering Analytics and Marketing toggle categories |
| 3 | Visitor accepts Analytics → loadMicrosoftClarity() is called |
| 4 | An inline <script> is appended to <head> that bootstraps the Clarity SDK using the project ID from NEXT_PUBLIC_CLARITY_PROJECT_ID |
| 5 | Clarity 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:
| Category | Services | Cookies |
|---|---|---|
| Analytics | Google Analytics, Microsoft Clarity | _ga, _gid, _clck, _clsk |
| Marketing | Meta 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()fromnext/navigationto 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_PATHto the pathname so the paths recorded in Clarity match the deployed URLs (important for GitHub Pages, which uses a repository-name prefix). - If
window.claritydoes 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:
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_PATHis set, the path is prefixed correctly - Root path — ensures
/does not produce a double-slash - Graceful degradation — no error is thrown when
window.clarityis 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
| Variable | Purpose | Default |
|---|---|---|
| NEXT_PUBLIC_CLARITY_PROJECT_ID | Clarity project identifier, injected into the tracking script | XXXXXXXXXX |
| NEXT_PUBLIC_BASE_PATH | Prepended 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
_clckand_clskwhen 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
- Google Analytics Integration — quantitative impact measurement and the Verified Visitors methodology
- Cloudflare Integration — CDN, DNS, and security layer
- All Technical Integrations
