Every analytics tool tells you what happened — a number went up, a number went down. Almost none of them show you why. Microsoft Clarity is the exception, and it's free with no sampling, which is a combination I still don't quite believe exists.
I add Clarity to nearly every Next.js app I ship, including Intavue, my AI interview-prep platform. It's the first tool I reach for when a conversion number looks wrong, because it lets me watch real sessions instead of guessing. This is exactly how I wire it up.
What Microsoft Clarity actually gives you
Clarity is a behavioral analytics tool from Microsoft. The three things I use it for:
- Session recordings — real anonymized replays of what users did, including mouse movement, scrolls, and clicks.
- Heatmaps — click, scroll, and area maps that show where attention actually goes versus where you assumed it would.
- Rage clicks and dead clicks — Clarity automatically flags frustration signals, like a button people keep clicking that isn't wired up.
Unlike most tools, it doesn't sample your traffic and it doesn't cap your sessions. You get all of it, for free. The tradeoff is that it's behavioral, not a full product-analytics suite — pair it with something event-based if you need funnels with math behind them.
Get your project ID
Create a project at clarity.microsoft.com, and it'll hand you a project ID — a short string like abcde12345. That's the only credential you need. Put it in .env.local so it isn't hardcoded:
bashNEXT_PUBLIC_CLARITY_PROJECT_ID=abcde12345
It has to be prefixed with NEXT_PUBLIC_ because Clarity runs in the browser.
The right way to add Clarity in the App Router
The mistake I see most often is people dropping the raw Clarity snippet directly into a Server Component, or worse, into the <head> with a plain <script> tag. In the App Router that either errors or blocks rendering. Use next/script with the afterInteractive strategy so Clarity loads after the page is usable and never competes with your content.
Create a small client component:
tsx// components/clarity.tsx 'use client'; import Script from 'next/script'; export default function Clarity() { const id = process.env.NEXT_PUBLIC_CLARITY_PROJECT_ID; // Don't ship the tag when there's no ID (e.g. local dev, previews). if (!id) return null; return ( <Script id="ms-clarity" strategy="afterInteractive"> {` (function(c,l,a,r,i,t,y){ c[a]=c[a]||function(){(c[a].q=c[a].q||[]).push(arguments)}; t=l.createElement(r);t.async=1;t.src="https://www.clarity.ms/tag/"+i; y=l.getElementsByTagName(r)[0];y.parentNode.insertBefore(t,y); })(window, document, "clarity", "script", "${id}"); `} </Script> ); }
Then drop it into your root layout:
tsx// app/layout.tsx import Clarity from '@/components/clarity'; export default function RootLayout({ children }: { children: React.ReactNode }) { return ( <html lang="en"> <body> {children} <Clarity /> </body> </html> ); }
That's the whole integration. Deploy it, open the app, and within a minute or two your first session shows up in the dashboard.
Prefer the official package? Use it
Microsoft ships an official npm package that's cleaner if you plan to call Clarity's API (tags, identify, custom events):
bashnpm install @microsoft/clarity
tsx// components/clarity.tsx 'use client'; import { useEffect } from 'react'; import Clarity from '@microsoft/clarity'; export default function ClarityProvider() { useEffect(() => { const id = process.env.NEXT_PUBLIC_CLARITY_PROJECT_ID; if (id) Clarity.init(id); }, []); return null; }
Both approaches work. I reach for the package whenever I want to tag sessions, which I'll get to below.
Only load it in production
You don't want your own local clicking and your teammates' preview-deploy sessions polluting real user data. Gate it on the environment:
tsxconst id = process.env.NEXT_PUBLIC_CLARITY_PROJECT_ID; const enabled = process.env.NODE_ENV === 'production'; if (!id || !enabled) return null;
On Vercel you can go further and check process.env.VERCEL_ENV === 'production' so preview branches stay clean too.
Consent and CSP — don't skip this
Clarity records sessions, so in the EU and UK you generally need consent before it runs. The official package makes this explicit — initialize without consent, then call it once the user agrees:
tsxClarity.consent(); // call after the user accepts your cookie banner
The other thing that bites people is Content-Security-Policy. If you've locked down your headers — and you should, which I wrote about in securing Next.js apps — you'll need to allow Clarity's domains, or the browser silently blocks the tag:
script-src 'self' https://www.clarity.ms;
connect-src 'self' https://*.clarity.ms https://c.bing.com;
If Clarity "isn't working," a blocked CSP directive is the first thing to check in your console.
Tag and identify real users
Anonymous recordings are useful; recordings you can filter are far more useful. On Intavue I tag sessions by plan and by which part of the flow the user is in, so I can pull up "every free user who abandoned a mock interview" in one click:
tsximport Clarity from '@microsoft/clarity'; // Group and segment sessions Clarity.setTag('plan', 'free'); Clarity.setTag('feature', 'voice-interview'); // Attach a stable, non-PII identifier Clarity.identify('user-1a2b3c'); // Mark a meaningful moment Clarity.event('interview_completed');
Never pass real emails or names to identify — use an opaque ID. Clarity masks input fields by default, but you're responsible for what you hand it.
How I actually use the data
Setup is the easy part. The habit that pays off is opening Clarity with a question, not browsing recordings at random. A few filters I lean on:
- Sort by rage clicks to find broken or confusing UI fast.
- Filter to sessions with JavaScript errors to see the exact steps that triggered a bug.
- Watch recordings of users who hit a key page but didn't convert — this is where the real product insights live.
I've caught more UX problems in ten minutes of Clarity recordings than in weeks of staring at aggregate charts.
Wrapping up
Adding Microsoft Clarity to Next.js is genuinely a ten-minute job: one client component, one env var, afterInteractive, and mind your CSP. The leverage isn't in the install — it's in the discipline of watching how people actually use what you built. If you're shipping a product like Intavue, that feedback loop is worth more than another dashboard of numbers you can't act on.