All Articles
Analytics & Tracking

Adding Microsoft Clarity to a Nuxt App the Right Way (Behind Real Cookie Consent)

Shahid MalikBy Shahid MalikSeptember 4, 20266 min read

Purpose, steps, and the actual benefit of wiring Microsoft Clarity into Tabeer.ai's Nuxt frontend — loaded only after a visitor consents, verified standalone before it ever touched production, matching the same pattern already used for Smartlook and PostHog.

I added Microsoft Clarity to Tabeer.ai's Nuxt frontend — free session recordings and heatmaps from Microsoft, no spend cap, no seat limit. The integration itself is a handful of lines. What made it worth writing up is the same thing that's made every other analytics integration on this project worth writing up: doing it in a way that doesn't silently break the moment a real visitor loads the page, and doing it in a way that actually respects the cookie consent this project already has in place.

Purpose

Tabeer.ai already runs Smartlook for session recordings and PostHog for product analytics. Clarity isn't replacing either — it's a free, zero-limit third data point specifically for heatmaps and session replay, useful for exactly the kind of UX question a funnel metric can't answer on its own: not just that students drop off on a particular exam page, but where they were looking and clicking right before they left. Free, with no session cap, makes it worth having alongside the others rather than instead of them.

Steps

1. Get the snippet. Clarity's dashboard gives you a standard tracking snippet with your project ID baked into the script URL:

<script type="text/javascript">
    (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", "PROJECT_ID");
</script>

2. Don't just paste it into the <head>. This project deliberately avoids npm packages for third-party scripts like this — Sentry, Smartlook, and PostHog are all loaded via their vanilla JS snippets rather than framework-specific SDK packages, which keeps SSR simple and avoids shipping analytics code in the server bundle for something that only ever needs to run in the browser. Clarity gets the same treatment: the project ID goes into Nuxt's runtime config, gated behind an environment variable so it's fully disableable with nothing set:

// nuxt.config.ts
runtimeConfig: {
  public: {
    clarityId: process.env.NUXT_PUBLIC_CLARITY_ID || '',
  },
},

3. Load it only after consent — not on page load. This is the part worth being deliberate about. The snippet becomes a function, called only from inside the existing cookie-consent component's "granted" path, alongside the other two:

let clarityLoaded = false
function loadClarity() {
  if (!import.meta.client || clarityLoaded || !config.public.clarityId) return
  clarityLoaded = true
  ;(function (c, l, a, r, i) {
    c[a] = c[a] || function (...args) { (c[a].q = c[a].q || []).push(args) }
    const t = l.createElement(r)
    t.async = true
    t.src = 'https://www.clarity.ms/tag/' + i
    const y = l.getElementsByTagName(r)[0]
    y.parentNode.insertBefore(t, y)
  })(window, document, 'clarity', 'script', config.public.clarityId)
}

Called from both places the app already handles consent: once when a returning visitor's stored choice is "granted," and once when a first-time visitor clicks "Accept." Never called on a bare page load, never called for a visitor who declines.

4. Verify the snippet logic before it touches production — not after. A hand-transcribed analytics snippet earlier in this project's history had a real bug: a mistranslated ternary that overwrote the tracking queue object with a literal string, throwing on every page load once cookies were accepted. The lesson stuck. Before deploying Clarity, the exact queue-stub logic got run standalone in plain Node with a mocked window/document, confirming the queue array actually accumulates calls correctly instead of assuming a snippet copy-paste is automatically correct:

node -e "
const window = {};
const document = { createElement: () => ({}), getElementsByTagName: () => [{ parentNode: { insertBefore: () => {} } }] };
(function (c, l, a, r, i) {
  c[a] = c[a] || function (...args) { (c[a].q = c[a].q || []).push(args) };
  // ...
})(window, document, 'clarity', 'script', 'test-id');
window.clarity('set', 'test', 'value');
console.log(window.clarity.q); // should log the queued call, not throw
"

That thirty-second check is cheap insurance against shipping a broken snippet to every visitor on the site.

Solutions / Benefits

  • Consent-gated by construction, not by convention. Because loading is a function call from inside the one component that owns consent state, there's no code path where Clarity (or Smartlook, or PostHog) can load before a visitor has actually agreed to it — the alternative, remembering to wrap every analytics <script> tag in a conditional, is exactly the kind of thing that gets forgotten under deadline pressure.
  • Zero cost, zero session cap, which matters for an education platform where traffic is naturally spiky around exam dates — a paid session-recording tool with a monthly cap would either throttle exactly when the data is most valuable, or need active budget management most teams don't have bandwidth for.
  • Consistent architecture across every third-party script, not a one-off. Anyone maintaining this project later finds Sentry, Smartlook, PostHog, and now Clarity all following the identical pattern — runtime-config-gated, loaded from one place, verified before deploy — rather than four different integration styles accumulated over time.
  • Heatmap + session replay data that a metrics dashboard can't give you. PostHog answers "how many students reached this page and what did they click." Clarity answers "what did their cursor do in the five seconds before they left" — genuinely different signal, useful for exactly the UX debugging a funnel number can't do on its own.

If you're adding analytics to a Nuxt or Django project and want it done in a way that respects consent by default rather than as an afterthought, get in touch.

Related Articles

Shahid Malik - AI-First Odoo Consultant

Shahid Malik

AI-First Odoo ERP Specialist

Shahid Malik is an AI-first Odoo consultant helping businesses solve complex ERP and business process challenges. His work combines Odoo consulting, process optimization, automation, integrations, migrations, and practical AI solutions to build scalable and reliable business systems.

Book a consultation for your Odoo project
Discuss Your Odoo Project