Skip to content

Commit ac3d1de

Browse files
authored
⚡ Performance: Defer Microsoft Clarity (#1748)
* refactor(MicrosoftClarity): simplify component structure and improve script loading * feat(useInteractionScriptLoader): add mousemove event listener for improved script loading on user interactions * feat(useInteractionScriptLoader): add keydown event listener for enhanced script loading on user interactions * docs(MicrosoftClarity): add comments to clarify the queue pattern for script loading * refactor(MicrosoftClarity): remove redundant comment in component return statement * refactor(MicrosoftClarity): clean up import statement for consistency
1 parent 1d819ac commit ac3d1de

File tree

4 files changed

+38
-24
lines changed

4 files changed

+38
-24
lines changed

packages/components/src/engine/renderApplicationScripts.tsx

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,7 @@ export const RenderApplicationScripts = ({
4545
)}
4646

4747
{!!site.isomerMsClarityId && (
48-
<MicrosoftClarity
49-
msClarityId={site.isomerMsClarityId}
50-
ScriptComponent={ScriptComponent}
51-
/>
48+
<MicrosoftClarity msClarityId={site.isomerMsClarityId} />
5249
)}
5350

5451
{/* Ensures that the webchat widget only loads after the page has loaded */}

packages/components/src/hooks/useInteractionScriptLoader.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,12 @@ export const useInteractionScriptLoader = ({
2121

2222
const triggerLoad = () => setShouldLoad(true)
2323

24-
// Load script on user interactions (scroll, click, touchstart)
24+
// Load script on user interactions (scroll, click, touchstart, mousemove, keydown)
2525
useEventListener("scroll", triggerLoad, documentRef, { passive: true })
2626
useEventListener("click", triggerLoad, documentRef) // as we might need to call preventDefault
2727
useEventListener("touchstart", triggerLoad, documentRef, { passive: true })
28+
useEventListener("mousemove", triggerLoad, documentRef, { passive: true })
29+
useEventListener("keydown", triggerLoad, documentRef, { passive: true })
2830

2931
// Load script after timeout if user doesn't interact
3032
useTimeout(triggerLoad, timeout)
Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
import type { IsomerSiteProps, ScriptComponentType } from "~/types"
1+
import type { IsomerSiteProps } from "~/types"
22

33
export interface MicrosoftClarityProps {
44
msClarityId: NonNullable<IsomerSiteProps["isomerMsClarityId"]>
5-
ScriptComponent?: ScriptComponentType
65
}
Lines changed: 33 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,36 @@
1+
"use client"
2+
3+
import { useEffect } from "react"
4+
15
import type { MicrosoftClarityProps } from "~/interfaces"
6+
import { useInteractionScriptLoader } from "~/hooks/useInteractionScriptLoader"
7+
8+
export const MicrosoftClarity = ({ msClarityId }: MicrosoftClarityProps) => {
9+
// Step 1: insert the tiny inline init function
10+
// If code calls window.clarity() before the script loads, it would fail because window.clarity doesn't exist yet.
11+
// The function below (taken from the Clarity docs) implements a queue pattern:
12+
// 1. Creates a placeholder window.clarity function that queues calls
13+
// 2. Stores calls in window.clarity.q array
14+
// 3. When the real Clarity script loads, it processes all queued calls
15+
useEffect(() => {
16+
// to not render during static site generation on the server
17+
if (typeof window === "undefined") return
18+
19+
// @ts-expect-error - Clarity is not typed
20+
if (!window.clarity) {
21+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any
22+
;(window as any).clarity = function () {
23+
// @ts-expect-error - Clarity is not typed
24+
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, prefer-rest-params
25+
;(window.clarity.q = window.clarity.q || []).push(arguments)
26+
}
27+
}
28+
}, [])
29+
30+
// Step 2: delayed loader for the actual Clarity script
31+
useInteractionScriptLoader({
32+
src: `https://www.clarity.ms/tag/${msClarityId}`,
33+
})
234

3-
export const MicrosoftClarity = ({
4-
msClarityId,
5-
ScriptComponent,
6-
}: MicrosoftClarityProps) => {
7-
return (
8-
<ScriptComponent
9-
id={`_next-ms-clarity-init-${msClarityId}`}
10-
strategy="afterInteractive" // next/script's default but just in case Vercel changes it in the future
11-
dangerouslySetInnerHTML={{
12-
__html: `(function(c,l,a,r,i,t,y){
13-
c[a]=c[a]||function(){(c[a].q=c[a].q||[]).push(arguments)};
14-
t=l.createElement(r);t.async=1;t.src="https://www.clarity.ms/tag/"+i;
15-
y=l.getElementsByTagName(r)[0];y.parentNode.insertBefore(t,y);
16-
})(window, document, "clarity", "script", "${msClarityId}");`,
17-
}}
18-
/>
19-
)
35+
return null
2036
}

0 commit comments

Comments
 (0)