A Complete Practical Guide to Integrating Microsoft Clarity into a Starlight Documentation Site
From Data Insight to User Growth: A Complete Guide to Integrating Clarity Analytics into the HagiCode Blog
Section titled “From Data Insight to User Growth: A Complete Guide to Integrating Clarity Analytics into the HagiCode Blog”This article shares how to elegantly integrate Microsoft Clarity into a Starlight documentation site so you can clearly understand user behavior while still staying privacy-compliant. This solution is distilled from our implementation experience in the HagiCode project, and we hope it gives some useful reference to anyone else wrestling with analytics.
Background: Why Did We Need Clarity?
Section titled “Background: Why Did We Need Clarity?”The following code shows how to dynamically inject the Microsoft Clarity script in an Astro integration based on environment variables, loading it in production only when it is actually enabled.
105 | interface Props {106 | // Future extension: allow manually overriding the Project ID107 | projectId?: string;108 | }109 |110 | const {111 | projectId = import.meta.env.CLARITY_PROJECT_ID,112 | } = Astro.props;113 |114 | const isProduction = import.meta.env.PROD;115 | ---116 |117 | {isProduction && projectId && (118 | <script is:inline define:vars={{projectId}}>119 | (function(c,l,a,r,i,t,y){File: openspec/changes/archive/2026-01-30-microsoft-clarity-integration/design.md
While operating HagiCode, we kept running into a “black box” problem: we were producing content, but we had no clear view of how users were actually reading it. GitHub Stars can show some signal, but that feedback comes far too late. What we really needed to know was:
- Do users actually finish reading our tutorials?
- At which step do those complicated configuration docs drive people away?
- Is our SEO optimization really bringing in meaningful traffic?
There are many analytics tools on the market, such as Google Analytics (GA) and Microsoft Clarity. GA is powerful but more complex to configure, and it is also more tightly constrained by privacy regulations such as GDPR. Clarity, as Microsoft’s free heatmap tool, is not only intuitive to use but also relatively easier to work with from a privacy-compliance perspective, making it a great fit for technical documentation sites.
Our goal was very clear: seamlessly integrate Clarity into the HagiCode documentation site so that it works across all pages while still giving users the right to opt out for privacy compliance.
About HagiCode
Section titled “About HagiCode”HagiCode theme initialization logic: first read from local storage, then fall back to system preference, with dark mode as the default.
67 | function getInitialTheme(): Theme {68 | // 1. Check localStorage69 | const stored = localStorage.getItem('hagicode-theme');70 | if (stored) return stored as Theme;71 |72 | // 2. Detect system preference73 | const systemDark = window.matchMedia('(prefers-color-scheme: dark)').matches;74 | if (systemDark) return 'dark';75 |76 | // 3. Default to dark mode77 | return 'dark';78 | }79 | ```80 |81 | ### Decision 3: Theme application method82 |83 | **Choice**: set the `data-theme` attribute on the `<html>` root element84 |85 | **Alternative options**:86 |File: openspec/changes/archive/2026-01-29-theme-toggle-implementation/design.md
The approach shared in this article comes from our hands-on experience in the HagiCode project. HagiCode is an AI-based coding assistant tool, and during development we need to maintain a large body of technical documentation and blog content. To better understand user needs, we explored and implemented this analytics integration solution.
Technical Selection and Exploration
Section titled “Technical Selection and Exploration”At first, we discussed several integration approaches during the Proposal phase. Since we were using Starlight, a documentation framework built on Astro, the most obvious idea was to use Astro Hooks.
We first tried modifying astro.config.mjs and planned to inject the Clarity script during the build. Although that approach could guarantee global coverage, it lacked flexibility: we could not dynamically load or unload the script based on user preference.
Taking user experience and privacy control into account, we ultimately chose a component override approach. Starlight allows developers to override its internal components, which means we could take over the rendering logic for <footer> or <head> and precisely control when Clarity is loaded.
There was also a small detour here: originally, we wanted to create a layout wrapper named StarlightWrapper.astro. But during real debugging, we found that Starlight’s routing mechanism does not automatically invoke this custom wrapper, causing the script to fail on some pages. It was a classic “obvious assumption” pitfall and reminded us that we must deeply understand the framework’s rendering flow instead of blindly applying generic framework patterns.
Core Solution: Overriding the Footer Component
Section titled “Core Solution: Overriding the Footer Component”To ensure the Clarity script loads on all pages, including docs and blog posts, without breaking the original page structure, we chose to override Starlight’s Footer component.
Why the Footer?
Section titled “Why the Footer?”- Global presence: the Footer appears on almost all standard pages.
- Non-intrusive: placing the script in the Footer area, which is actually rendered near the bottom of the body, does not block the page’s critical rendering path (LCP), so the performance impact is minimal.
- Centralized logic: cookie consent logic can be handled in one place inside the component.
Implementation Steps
Section titled “Implementation Steps”1. Prepare a Clarity project
Section titled “1. Prepare a Clarity project”First, register at Microsoft Clarity and create a new project. Then get your Project ID, which looks like a string such as k8z2ab3xxx.
2. Configure environment variables
Section titled “2. Configure environment variables”The following demonstrates environment variable configuration and date-checking logic to implement conditional behavior during the Lunar New Year period. Please refer to the concrete implementation.
46 | function isLunarNewYearPeriod() {47 | const now = new Date();48 | const year = now.getFullYear();49 | const month = now.getMonth() + 1; // 1-1250 | const day = now.getDate();51 |52 | // Lunar New Year period in 2025, Year of the Snake (January 29 - February 12)53 | if (year === 2025) {54 | if (month === 1 && day >= 29) return true;55 | if (month === 2 && day <= 12) return true;56 | }57 | // Lunar New Year period in 2026, Year of the Horse (February 17 - March 3)58 | if (year === 2026) {59 | if (month === 2 && day >= 17) return true;60 | if (month === 3 && day <= 3) return true;61 | }62 | return false;63 | }64 |65 | const stored = localStorage.getItem('starlight-theme');File: src/pages/index.astro
For safety, do not hardcode the ID. It is recommended to store it in an environment variable.
Create a .env file in the project root:
# Microsoft Clarity IDPUBLIC_CLARITY_ID="your_Clarity_ID"3. Create the override component
Section titled “3. Create the override component”The following is an implementation that listens for system theme changes, showing how to follow the system theme only when the user has not set one manually.
445 | const handleChange = (e: MediaQueryListEvent) => {446 | // Only follow the system when the user has not manually set a theme447 | if (!localStorage.getItem(THEME_KEY)) {448 | setThemeState(e.matches ? 'dark' : 'light');449 | }450 | };451 |452 | mediaQuery.addEventListener('change', handleChange);453 | return () => mediaQuery.removeEventListener('change', handleChange);454 | }, []);455 |456 | return { theme, toggleTheme, setTheme: manuallySetTheme };457 | }458 | ```459 |460 | #### 3. `src/components/ThemeButton.tsx` - Button component461 |462 | **Responsibility**: render the theme toggle button and handle user interaction463 |464 | **Component interface**:File: openspec/changes/archive/2026-01-29-theme-toggle-implementation/design.md
Create a file named StarlightFooter.astro under src/components/. Starlight will automatically recognize it and override the default Footer.
The core code logic is as follows:
---// 1. Import the original component to preserve its default behaviorimport DefaultFooter from '@astrojs/starlight/components/StarlightFooter.astro';
// 2. Read the environment variableconst clarityId = import.meta.env.PUBLIC_CLARITY_ID;
// 3. Define a simple injection script (inline approach)// Note: in production, it is recommended to move this logic into a separate .js file to take advantage of cachingconst initScript = `(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", "${clarityId}");`;---
<DefaultFooter {...Astro.props} />
{/* Inject the script only in production and only when the ID exists */}{import.meta.env.PROD && clarityId && ( <script is:inline define:vars={{ clarityId }}> {initScript} </script>)}Key points explained:
is:inline: tells Astro not to process the contents of this script tag and to output it directly into the HTML. This is critical for third-party analytics scripts; otherwise, Astro’s bundling optimization may cause the script to stop working.define:vars: an Astro 3+ feature that allows variables to be injected safely within scope.import.meta.env.PROD: ensures no meaningless analytics are generated during local development unless explicitly needed for debugging, keeping your data clean.
Advanced Topic: Privacy Compliance and Cookie Control
Section titled “Advanced Topic: Privacy Compliance and Cookie Control”Simply adding the code is not enough, especially in GDPR-regulated regions. We need to respect user choice.
HagiCode’s approach is to provide a simple toggle. While this is not a full-featured cookie banner, for a technical documentation site that mainly serves content, such cookies are often categorized as “necessary” or “analytics” cookies and can be disclosed in the privacy statement and enabled by default, or linked from the Footer to a privacy settings page.
If you need stricter control, you can combine it with localStorage to record the user’s choice:
This article will introduce TypeScript utility functions used for theme switching and persistence, using type safety and environment detection for stricter control.
367 | export function getInitialTheme(): Theme;368 | export function getSystemTheme(): Theme;369 | export function setTheme(theme: Theme): void;370 | export function applyTheme(theme: Theme): void;371 | ```372 |373 | **Design principles**:374 | - **Pure functions**: no side effects, except for `setTheme` and `applyTheme`375 | - **Type safety**: complete TypeScript type inference376 | - **Environment detection**: SSR-safe (`typeof window` check)377 | - **Single responsibility**: each function does only one thing378 |379 | **Key implementation**:380 | ```typescript381 | export function getInitialTheme(): Theme {382 | if (typeof window === 'undefined') return 'dark';383 |384 | const stored = localStorage.getItem(THEME_KEY);385 | if (stored === 'light' || stored === 'dark') return stored;386 |File: openspec/changes/archive/2026-01-29-theme-toggle-implementation/design.md
// Simple example: check whether the user has declined analyticsconst consent = localStorage.getItem('clarity_consent');if (consent !== 'denied') { // Run the Clarity initialization code above window.clarity('start', clarityId);}Lessons Learned and Pitfalls
Section titled “Lessons Learned and Pitfalls”As we rolled this solution out in HagiCode, we summarized several details that are easy to overlook:
-
StarlightWrapper.astrois a trap: As mentioned earlier, do not try to create a global Wrapper to inject the script. That approach does not work in Starlight. The correct solution is to override a specific component such asStarlightFooter.astroorStarlightHead.astro. -
Performance considerations for script placement: Although Clarity recommends placing the script in
<head>to ensure maximum data accuracy, for a documentation site, first-screen loading speed (LCP) directly affects SEO and user retention. We chose to place it in the Footer, near the bottom of the body. This may slightly miss a tiny amount of “bounce in seconds” user data, but it gives us a faster page-loading experience, which is a worthwhile trade-off. -
Interference from the development environment: Be sure to add the
import.meta.env.PRODcheck. In development mode, you will refresh pages frequently, which would otherwise generate a large amount of meaningless test data and pollute your Clarity dashboard.
Verifying the Result
Section titled “Verifying the Result”After deployment, you can view real-time data in the Clarity console. Usually within a few minutes, you will start seeing user heatmaps and recordings.
For HagiCode, this data helped us discover that:
- many users repeatedly revisit the “Quick Start” section, suggesting that our installation guidance may still not be intuitive enough.
- the “API Reference” page has the longest dwell time, confirming the needs of our core user group.
Conclusion
Section titled “Conclusion”Integrating Microsoft Clarity does not require complex server-side changes, nor does it require bringing in a heavy SDK.
By taking advantage of Starlight’s component override mechanism, we achieved site-wide analytics with nothing more than a lightweight StarlightFooter.astro component. This kind of “micro-integration” keeps the codebase clean while giving us the ability to understand user behavior.
If you are also operating a technical project, especially one like HagiCode where the documentation needs to keep evolving, I strongly recommend trying Clarity. The data will tell you where users are really struggling.
References
Section titled “References”- HagiCode GitHub Repository - See the configuration files from our real project
- Microsoft Clarity Official Documentation
- Starlight Component Override Guide
Thank you for reading. If you found this article useful, click the like button below 👍 so more people can discover it.
This content was created with AI-assisted collaboration, reviewed by me, and reflects my own views and position.
- Author: newbe36524
- Article URL: https://hagicode.com/blog/2026-02-04-starlight-docs-integration-microsoft-clarity/
- Copyright Notice: Unless otherwise stated, all articles on this blog are licensed under BY-NC-SA. Please include the original source when reposting.