Skip to content

Commit aab4777

Browse files
authored
feat: clarity (#91)
1 parent a5934d9 commit aab4777

File tree

6 files changed

+288
-3
lines changed

6 files changed

+288
-3
lines changed
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
---
2+
title: Clarity
3+
description: Use Clarity in your Nuxt app.
4+
links:
5+
- label: Source
6+
icon: i-simple-icons-github
7+
to: https://github.com/nuxt/scripts/blob/main/src/runtime/registry/clarity.ts
8+
size: xs
9+
---
10+
11+
[Clarity](https://clarity.microsoft.com/) by Microsoft is a screen recorder and heatmap tool that helps you understand how users interact with your website.
12+
13+
Nuxt Scripts provides a registry script composable `useScriptClarity` to easily integrate Clarity in your Nuxt app.
14+
15+
### Nuxt Config Setup
16+
17+
The simplest way to load Clarity globally in your Nuxt App is to use Nuxt config. Alternatively you can directly
18+
use the [useScriptClarity](#useScriptClarity) composable.
19+
20+
If you don't plan to send custom events you can use the [Environment overrides](https://nuxt.com/docs/getting-started/configuration#environment-overrides) to
21+
disable the script in development.
22+
23+
::code-group
24+
25+
```ts [Always enabled]
26+
export default defineNuxtConfig({
27+
scripts: {
28+
registry: {
29+
clarity: {
30+
id: 'YOUR_ID'
31+
}
32+
}
33+
}
34+
})
35+
```
36+
37+
```ts [Production only]
38+
export default defineNuxtConfig({
39+
$production: {
40+
scripts: {
41+
registry: {
42+
clarity: {
43+
id: 'YOUR_ID',
44+
}
45+
}
46+
}
47+
}
48+
})
49+
```
50+
51+
::
52+
53+
#### With Environment Variables
54+
55+
If you prefer to configure your id using environment variables.
56+
57+
```ts [nuxt.config.ts]
58+
export default defineNuxtConfig({
59+
scripts: {
60+
registry: {
61+
clarity: true,
62+
}
63+
},
64+
// you need to provide a runtime config to access the environment variables
65+
runtimeConfig: {
66+
public: {
67+
scripts: {
68+
clarity: {
69+
id: '', // NUXT_PUBLIC_SCRIPTS_CLARITY_ID
70+
},
71+
},
72+
},
73+
},
74+
})
75+
```
76+
77+
```text [.env]
78+
NUXT_PUBLIC_SCRIPTS_CLARITY_ID=<YOUR_ID>
79+
```
80+
81+
## useScriptClarity
82+
83+
The `useScriptClarity` composable lets you have fine-grain control over when and how Clarity is loaded on your site.
84+
85+
```ts
86+
const { clarity } = useScriptClarity({
87+
id: 'YOUR_ID'
88+
})
89+
// example
90+
clarity("identify", "custom-id", "custom-session-id", "custom-page-id", "friendly-name")
91+
```
92+
93+
Please follow the [Registry Scripts](/docs/guides/registry-scripts) guide to learn more about advanced usage.
94+
95+
### ClarityApi
96+
97+
```ts
98+
type ClarityFunctions = ((fn: 'start', options: { content: boolean, cookies: string[], dob: number, expire: number, projectId: string, upload: string }) => void)
99+
& ((fn: 'identify', id: string, session?: string, page?: string, userHint?: string) => Promise<{
100+
id: string
101+
session: string
102+
page: string
103+
userHint: string
104+
}>)
105+
& ((fn: 'consent') => void)
106+
& ((fn: 'set', key: any, value: any) => void)
107+
& ((fn: 'event', value: any) => void)
108+
& ((fn: 'upgrade', upgradeReason: any) => void)
109+
& ((fn: string, ...args: any[]) => void)
110+
111+
export interface ClarityApi {
112+
clarity: ClarityFunctions & {
113+
q: any[]
114+
v: string
115+
}
116+
}
117+
118+
```
119+
120+
### Config Schema
121+
122+
You must provide the options when setting up the script for the first time.
123+
124+
```ts
125+
export const ClarityOptions = object({
126+
/**
127+
* The Clarity token.
128+
*/
129+
id: pipe(string(), minLength(10)),
130+
})
131+
```
132+
133+
## Example
134+
135+
Using Clarity only in production while using `clarity` to send a conversion event.
136+
137+
::code-group
138+
139+
```vue [ConversionButton.vue]
140+
<script setup>
141+
const { clarity } = useScriptClarity()
142+
143+
// noop in development, ssr
144+
// just works in production, client
145+
function sendConversion() {
146+
clarity('event', 'conversion')
147+
}
148+
</script>
149+
150+
<template>
151+
<div>
152+
<button @click="sendConversion">
153+
Send Conversion
154+
</button>
155+
</div>
156+
</template>
157+
```
158+
159+
```ts [nuxt.config.ts Mock development]
160+
import { isDevelopment } from 'std-env'
161+
162+
export default defineNuxtConfig({
163+
scripts: {
164+
registry: {
165+
clarity: isDevelopment
166+
? 'mock' // script won't load unless manually callined load()
167+
: {
168+
id: 'YOUR_ID',
169+
},
170+
},
171+
},
172+
})
173+
```
174+
175+
::

docs/pages/index.vue

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -189,12 +189,13 @@ const contributors = useRuntimeConfig().public.contributors
189189
<div class="relative hidden xl:block">
190190
<div class="absolute -z-1 -right-[450px] -top-[200px]">
191191
<div class="w-[450px] grid-transform justify-center items-center grid grid-cols-4 ">
192-
<a v-for="(script, key) in registry.slice(0, 16)" :key="key" ref="card" :href="`/scripts/${script.category}/${script.label.toLowerCase().replace(/ /g, '-')}`" class="card py-5 px-3 rounded block" :style="{ zIndex: key }">
192+
<a v-for="(script, key) in registry.filter(s => s.label !== 'Carbon Ads').slice(0, 16)" :key="key" ref="card" :href="`/scripts/${script.category}/${script.label.toLowerCase().replace(/ /g, '-')}`" class="card py-5 px-3 rounded block" :style="{ zIndex: key }">
193193
<template v-if="typeof script.logo !== 'string'">
194194
<div class="logo h-12 w-auto block dark:hidden" v-html="script.logo.light" />
195195
<div class="logo h-12 w-auto hidden dark:block" v-html="script.logo.dark" />
196196
</template>
197-
<div v-else class="logo h-10 w-auto" v-html="script.logo" />
197+
<div v-else-if="script.logo.startsWith('<svg')" class="logo h-10 w-auto" v-html="script.logo" />
198+
<img v-else class="h-10 w-auto mx-auto logo" :src="script.logo">
198199
</a>
199200
</div>
200201
</div>

docs/pages/scripts.vue

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,8 @@ const scriptsCategories = useScriptsRegistry().reduce((acc, script) => {
8181
<div class="logo h-10 w-auto block dark:hidden" v-html="script.logo.light" />
8282
<div class="logo h-10 w-auto hidden dark:block" v-html="script.logo.dark" />
8383
</template>
84-
<div v-else class="logo h-10 w-auto" v-html="script.logo" />
84+
<div v-else-if="script.logo.startsWith('<svg')" class="logo h-10 w-auto" v-html="script.logo" />
85+
<img v-else class="h-10 w-auto mx-auto logo" :src="script.logo">
8586
</div>
8687
<div class="text-gray-500 text-sm font-semibold">
8788
{{ script.label }}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<script lang="ts" setup>
2+
import { useHead, useScriptClarity } from '#imports'
3+
4+
useHead({
5+
title: 'Clarity',
6+
})
7+
8+
// composables return the underlying api as a proxy object and a $script with the script state
9+
const { $script } = useScriptClarity({
10+
id: 'mqk2m9dr2v',
11+
})
12+
</script>
13+
14+
<template>
15+
<div>
16+
<ClientOnly>
17+
<div>
18+
status: {{ $script.status }}
19+
</div>
20+
</ClientOnly>
21+
</div>
22+
</template>

src/registry.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import type { NpmInput } from './runtime/registry/npm'
66
import type { PlausibleAnalyticsInput } from './runtime/registry/plausible-analytics'
77
import type { RegistryScripts } from './runtime/types'
88
import type { GoogleAdsenseInput } from './runtime/registry/google-adsense'
9+
import type { ClarityInput } from './runtime/registry/clarity'
910

1011
// avoid nuxt/kit dependency here so we can use in docs
1112

@@ -139,6 +140,18 @@ export const registry: (resolve?: (s: string) => string) => RegistryScripts = (r
139140
from: resolve('./runtime/registry/hotjar'),
140141
},
141142
},
143+
{
144+
label: 'Clarity',
145+
scriptBundling(options?: ClarityInput) {
146+
return `https://www.clarity.ms/tag/${options?.id}`
147+
},
148+
logo: `https://store-images.s-microsoft.com/image/apps.29332.512b1d3d-80ec-4aec-83bb-411008d2f7cd.76371b6f-9386-463f-bfb0-b75cffb86a4f.bd99f4b1-b18e-4380-aa79-93768763c90d.png`,
149+
category: 'marketing',
150+
import: {
151+
name: 'useScriptClarity',
152+
from: resolve('./runtime/registry/clarity'),
153+
},
154+
},
142155
// payments
143156
{
144157
label: 'Stripe',

src/runtime/registry/clarity.ts

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import { useRegistryScript } from '../utils'
2+
import { minLength, object, string, pipe } from '#nuxt-scripts-validator'
3+
import type { RegistryScriptInput } from '#nuxt-scripts'
4+
5+
/**
6+
* <script type="text/javascript">
7+
* (function(c,l,a,r,i,t,y){
8+
* c[a]=c[a]||function(){(c[a].q=c[a].q||[]).push(arguments)};
9+
* t=l.createElement(r);t.async=1;t.src="https://www.clarity.ms/tag/"+i;
10+
* y=l.getElementsByTagName(r)[0];y.parentNode.insertBefore(t,y);
11+
* })(window, document, "clarity", "script", "mpy5c6k7xi");
12+
* </script>
13+
*/
14+
15+
type ClarityFunctions = ((fn: 'start', options: { content: boolean, cookies: string[], dob: number, expire: number, projectId: string, upload: string }) => void)
16+
& ((fn: 'identify', id: string, session?: string, page?: string, userHint?: string) => Promise<{
17+
id: string
18+
session: string
19+
page: string
20+
userHint: string
21+
}>)
22+
& ((fn: 'consent') => void)
23+
& ((fn: 'set', key: any, value: any) => void)
24+
& ((fn: 'event', value: any) => void)
25+
& ((fn: 'upgrade', upgradeReason: any) => void)
26+
& ((fn: string, ...args: any[]) => void)
27+
28+
export interface ClarityApi {
29+
clarity: ClarityFunctions & {
30+
q: any[]
31+
v: string
32+
}
33+
}
34+
35+
declare global {
36+
interface Window extends ClarityApi {}
37+
}
38+
39+
export const ClarityOptions = object({
40+
/**
41+
* The Clarity token.
42+
*/
43+
id: pipe(string(), minLength(10)),
44+
})
45+
46+
export type ClarityInput = RegistryScriptInput<typeof ClarityOptions>
47+
48+
export function useScriptClarity<T extends ClarityApi>(
49+
_options?: ClarityInput,
50+
) {
51+
return useRegistryScript<T, typeof ClarityOptions>(
52+
'clarity',
53+
options => ({
54+
scriptInput: {
55+
src: `https://www.clarity.ms/tag/${options.id}`,
56+
},
57+
schema: import.meta.dev ? ClarityOptions : undefined,
58+
scriptOptions: {
59+
use() {
60+
return { clarity: window.clarity }
61+
},
62+
},
63+
clientInit: import.meta.server
64+
? undefined
65+
: () => {
66+
window.clarity = window.clarity || function () {
67+
(window.clarity.q = window.clarity.q || []).push(arguments)
68+
}
69+
},
70+
}),
71+
_options,
72+
)
73+
}

0 commit comments

Comments
 (0)