|
| 1 | +--- |
| 2 | +title: "How to add analytics to Nuxt" |
| 3 | +description: "Add privacy-first analytics to your Nuxt app in under 5 minutes with OpenPanel's official Nuxt module." |
| 4 | +difficulty: beginner |
| 5 | +timeToComplete: 5 |
| 6 | +date: 2025-01-07 |
| 7 | +cover: /content/cover-default.jpg |
| 8 | +team: OpenPanel Team |
| 9 | +steps: |
| 10 | + - name: "Install the module" |
| 11 | + anchor: "install" |
| 12 | + - name: "Configure the module" |
| 13 | + anchor: "setup" |
| 14 | + - name: "Track custom events" |
| 15 | + anchor: "events" |
| 16 | + - name: "Identify users" |
| 17 | + anchor: "identify" |
| 18 | + - name: "Set up server-side tracking" |
| 19 | + anchor: "server" |
| 20 | + - name: "Verify your setup" |
| 21 | + anchor: "verify" |
| 22 | +--- |
| 23 | + |
| 24 | +# How to add analytics to Nuxt |
| 25 | + |
| 26 | +This guide walks you through adding OpenPanel to a Nuxt 3 application. The official `@openpanel/nuxt` module makes integration effortless with auto-imported composables, automatic page view tracking, and a built-in proxy option to bypass ad blockers. |
| 27 | + |
| 28 | +OpenPanel is an open-source alternative to Mixpanel and Google Analytics. It uses cookieless tracking by default, so you won't need cookie consent banners for basic analytics. |
| 29 | + |
| 30 | +## Prerequisites |
| 31 | + |
| 32 | +- A Nuxt 3 project |
| 33 | +- An OpenPanel account ([sign up free](https://dashboard.openpanel.dev/onboarding)) |
| 34 | +- Your Client ID from the OpenPanel dashboard |
| 35 | + |
| 36 | +## Install the module [#install] |
| 37 | + |
| 38 | +Start by installing the OpenPanel Nuxt module. This package includes everything you need for client-side tracking, including the auto-imported `useOpenPanel` composable. |
| 39 | + |
| 40 | +```bash |
| 41 | +npm install @openpanel/nuxt |
| 42 | +``` |
| 43 | + |
| 44 | +If you prefer pnpm or yarn, those work too. |
| 45 | + |
| 46 | +## Configure the module [#setup] |
| 47 | + |
| 48 | +Add the module to your `nuxt.config.ts` and configure it with your Client ID. The module automatically sets up page view tracking and makes the `useOpenPanel` composable available throughout your app. |
| 49 | + |
| 50 | +```ts title="nuxt.config.ts" |
| 51 | +export default defineNuxtConfig({ |
| 52 | + modules: ['@openpanel/nuxt'], |
| 53 | + openpanel: { |
| 54 | + clientId: 'your-client-id', |
| 55 | + trackScreenViews: true, |
| 56 | + trackOutgoingLinks: true, |
| 57 | + trackAttributes: true, |
| 58 | + }, |
| 59 | +}); |
| 60 | +``` |
| 61 | + |
| 62 | +That's it. Page views are now being tracked automatically as users navigate your app. |
| 63 | + |
| 64 | +### Configuration options |
| 65 | + |
| 66 | +| Option | Default | Description | |
| 67 | +|--------|---------|-------------| |
| 68 | +| `clientId` | — | Your OpenPanel client ID (required) | |
| 69 | +| `apiUrl` | `https://api.openpanel.dev` | The API URL to send events to | |
| 70 | +| `trackScreenViews` | `true` | Automatically track page views | |
| 71 | +| `trackOutgoingLinks` | `true` | Track clicks on external links | |
| 72 | +| `trackAttributes` | `true` | Track elements with `data-track` attributes | |
| 73 | +| `trackHashChanges` | `false` | Track hash changes in URL | |
| 74 | +| `disabled` | `false` | Disable all tracking | |
| 75 | +| `proxy` | `false` | Route events through your server | |
| 76 | + |
| 77 | +### Using environment variables |
| 78 | + |
| 79 | +For production applications, store your Client ID in environment variables. |
| 80 | + |
| 81 | +```ts title="nuxt.config.ts" |
| 82 | +export default defineNuxtConfig({ |
| 83 | + modules: ['@openpanel/nuxt'], |
| 84 | + openpanel: { |
| 85 | + clientId: process.env.NUXT_PUBLIC_OPENPANEL_CLIENT_ID, |
| 86 | + trackScreenViews: true, |
| 87 | + }, |
| 88 | +}); |
| 89 | +``` |
| 90 | + |
| 91 | +```bash title=".env" |
| 92 | +NUXT_PUBLIC_OPENPANEL_CLIENT_ID=your-client-id |
| 93 | +``` |
| 94 | + |
| 95 | +## Track custom events [#events] |
| 96 | + |
| 97 | +Page views only tell part of the story. To understand how users interact with your product, track custom events like button clicks, form submissions, or feature usage. |
| 98 | + |
| 99 | +### Using the composable |
| 100 | + |
| 101 | +The `useOpenPanel` composable is auto-imported, so you can use it directly in any component without importing anything. |
| 102 | + |
| 103 | +```vue title="components/SignupButton.vue" |
| 104 | +<script setup> |
| 105 | +const op = useOpenPanel(); |
| 106 | +
|
| 107 | +function handleClick() { |
| 108 | + op.track('button_clicked', { |
| 109 | + button_name: 'signup', |
| 110 | + button_location: 'hero', |
| 111 | + }); |
| 112 | +} |
| 113 | +</script> |
| 114 | +
|
| 115 | +<template> |
| 116 | + <button type="button" @click="handleClick">Sign Up</button> |
| 117 | +</template> |
| 118 | +``` |
| 119 | + |
| 120 | +### Accessing via useNuxtApp |
| 121 | + |
| 122 | +You can also access the OpenPanel instance through `useNuxtApp()` if you prefer. |
| 123 | + |
| 124 | +```vue |
| 125 | +<script setup> |
| 126 | +const { $openpanel } = useNuxtApp(); |
| 127 | +
|
| 128 | +$openpanel.track('my_event', { foo: 'bar' }); |
| 129 | +</script> |
| 130 | +``` |
| 131 | + |
| 132 | +### Track form submissions |
| 133 | + |
| 134 | +Form tracking helps you understand conversion rates and identify where users drop off. |
| 135 | + |
| 136 | +```vue title="components/ContactForm.vue" |
| 137 | +<script setup> |
| 138 | +const op = useOpenPanel(); |
| 139 | +const email = ref(''); |
| 140 | +
|
| 141 | +async function handleSubmit() { |
| 142 | + op.track('form_submitted', { |
| 143 | + form_name: 'contact', |
| 144 | + form_location: 'homepage', |
| 145 | + }); |
| 146 | + |
| 147 | + // Your form submission logic |
| 148 | +} |
| 149 | +</script> |
| 150 | +
|
| 151 | +<template> |
| 152 | + <form @submit.prevent="handleSubmit"> |
| 153 | + <input v-model="email" type="email" placeholder="Enter your email" /> |
| 154 | + <button type="submit">Submit</button> |
| 155 | + </form> |
| 156 | +</template> |
| 157 | +``` |
| 158 | + |
| 159 | +### Use data attributes for declarative tracking |
| 160 | + |
| 161 | +The SDK supports declarative tracking using `data-track` attributes. This is useful for simple click tracking without writing JavaScript. |
| 162 | + |
| 163 | +```vue |
| 164 | +<template> |
| 165 | + <button |
| 166 | + data-track="button_clicked" |
| 167 | + data-track-button_name="signup" |
| 168 | + data-track-button_location="hero" |
| 169 | + > |
| 170 | + Sign Up |
| 171 | + </button> |
| 172 | +</template> |
| 173 | +``` |
| 174 | + |
| 175 | +When a user clicks this button, OpenPanel automatically sends a `button_clicked` event with the specified properties. This requires `trackAttributes: true` in your configuration. |
| 176 | + |
| 177 | +## Identify users [#identify] |
| 178 | + |
| 179 | +Anonymous tracking is useful, but identifying users unlocks more valuable insights. You can track behavior across sessions, segment users by properties, and build cohort analyses. |
| 180 | + |
| 181 | +Call `identify` after a user logs in or when you have their information available. |
| 182 | + |
| 183 | +```vue title="components/UserProfile.vue" |
| 184 | +<script setup> |
| 185 | +const op = useOpenPanel(); |
| 186 | +const props = defineProps(['user']); |
| 187 | +
|
| 188 | +watch(() => props.user, (user) => { |
| 189 | + if (user) { |
| 190 | + op.identify({ |
| 191 | + profileId: user.id, |
| 192 | + firstName: user.firstName, |
| 193 | + lastName: user.lastName, |
| 194 | + email: user.email, |
| 195 | + properties: { |
| 196 | + plan: user.plan, |
| 197 | + signupDate: user.createdAt, |
| 198 | + }, |
| 199 | + }); |
| 200 | + } |
| 201 | +}, { immediate: true }); |
| 202 | +</script> |
| 203 | +
|
| 204 | +<template> |
| 205 | + <div>Welcome, {{ user?.firstName }}!</div> |
| 206 | +</template> |
| 207 | +``` |
| 208 | + |
| 209 | +### Set global properties |
| 210 | + |
| 211 | +Properties set with `setGlobalProperties` are included with every event. This is useful for app version tracking, feature flags, or A/B test variants. |
| 212 | + |
| 213 | +```vue title="app.vue" |
| 214 | +<script setup> |
| 215 | +const op = useOpenPanel(); |
| 216 | +
|
| 217 | +onMounted(() => { |
| 218 | + op.setGlobalProperties({ |
| 219 | + app_version: '1.0.0', |
| 220 | + environment: useRuntimeConfig().public.environment, |
| 221 | + }); |
| 222 | +}); |
| 223 | +</script> |
| 224 | +``` |
| 225 | + |
| 226 | +### Clear user data on logout |
| 227 | + |
| 228 | +When users log out, clear the stored profile data to ensure subsequent events aren't associated with the previous user. |
| 229 | + |
| 230 | +```vue title="components/LogoutButton.vue" |
| 231 | +<script setup> |
| 232 | +const op = useOpenPanel(); |
| 233 | +
|
| 234 | +function handleLogout() { |
| 235 | + op.clear(); |
| 236 | + navigateTo('/login'); |
| 237 | +} |
| 238 | +</script> |
| 239 | +
|
| 240 | +<template> |
| 241 | + <button @click="handleLogout">Logout</button> |
| 242 | +</template> |
| 243 | +``` |
| 244 | + |
| 245 | +## Set up server-side tracking [#server] |
| 246 | + |
| 247 | +For tracking events in server routes, API endpoints, or server middleware, use the `@openpanel/sdk` package. Server-side tracking requires a client secret for authentication. |
| 248 | + |
| 249 | +### Install the SDK |
| 250 | + |
| 251 | +```bash |
| 252 | +npm install @openpanel/sdk |
| 253 | +``` |
| 254 | + |
| 255 | +### Create a server instance |
| 256 | + |
| 257 | +```ts title="server/utils/op.ts" |
| 258 | +import { OpenPanel } from '@openpanel/sdk'; |
| 259 | + |
| 260 | +export const op = new OpenPanel({ |
| 261 | + clientId: process.env.OPENPANEL_CLIENT_ID!, |
| 262 | + clientSecret: process.env.OPENPANEL_CLIENT_SECRET!, |
| 263 | +}); |
| 264 | +``` |
| 265 | + |
| 266 | +### Track events in server routes |
| 267 | + |
| 268 | +```ts title="server/api/webhook.post.ts" |
| 269 | +export default defineEventHandler(async (event) => { |
| 270 | + const body = await readBody(event); |
| 271 | + |
| 272 | + await op.track('webhook_received', { |
| 273 | + source: body.source, |
| 274 | + event_type: body.type, |
| 275 | + }); |
| 276 | + |
| 277 | + return { success: true }; |
| 278 | +}); |
| 279 | +``` |
| 280 | + |
| 281 | +Never expose your client secret on the client side. Keep it in server-only code. |
| 282 | + |
| 283 | +### Awaiting in serverless environments |
| 284 | + |
| 285 | +If you're deploying to a serverless platform like Vercel or Netlify, make sure to await the tracking call to ensure it completes before the function terminates. |
| 286 | + |
| 287 | +```ts |
| 288 | +export default defineEventHandler(async (event) => { |
| 289 | + // Always await in serverless environments |
| 290 | + await op.track('my_server_event', { foo: 'bar' }); |
| 291 | + return { message: 'Event logged!' }; |
| 292 | +}); |
| 293 | +``` |
| 294 | + |
| 295 | +## Bypass ad blockers with proxy [#proxy] |
| 296 | + |
| 297 | +Many ad blockers block requests to third-party analytics domains. The Nuxt module includes a built-in proxy that routes events through your own server. |
| 298 | + |
| 299 | +Enable the proxy option in your configuration: |
| 300 | + |
| 301 | +```ts title="nuxt.config.ts" |
| 302 | +export default defineNuxtConfig({ |
| 303 | + modules: ['@openpanel/nuxt'], |
| 304 | + openpanel: { |
| 305 | + clientId: 'your-client-id', |
| 306 | + proxy: true, // Routes events through /api/openpanel/* |
| 307 | + }, |
| 308 | +}); |
| 309 | +``` |
| 310 | + |
| 311 | +When `proxy: true` is set: |
| 312 | +- The module automatically sets `apiUrl` to `/api/openpanel` |
| 313 | +- A server handler is registered at `/api/openpanel/**` |
| 314 | +- All tracking requests route through your Nuxt server |
| 315 | + |
| 316 | +This makes tracking requests invisible to browser extensions that block third-party analytics. |
| 317 | + |
| 318 | +## Verify your setup [#verify] |
| 319 | + |
| 320 | +Open your Nuxt app in the browser and navigate between a few pages. Interact with elements that trigger custom events. Then open your [OpenPanel dashboard](https://dashboard.openpanel.dev) and check the Real-time view to see events appearing within seconds. |
| 321 | + |
| 322 | +If events aren't showing up, check the browser console for errors. The most common issues are: |
| 323 | +- Incorrect client ID |
| 324 | +- Ad blockers intercepting requests (enable the proxy option) |
| 325 | +- Client ID exposed in server-only code |
| 326 | + |
| 327 | +The Network tab in your browser's developer tools can help you confirm that requests are being sent. |
| 328 | + |
| 329 | +## Next steps |
| 330 | + |
| 331 | +The [Nuxt SDK reference](/docs/sdks/nuxt) covers additional features like incrementing user properties and event filtering. If you're interested in understanding how OpenPanel handles privacy, read our article on [cookieless analytics](/articles/cookieless-analytics). |
| 332 | + |
| 333 | +<Faqs> |
| 334 | +<FaqItem question="Does OpenPanel work with Nuxt 3 and Nuxt 4?"> |
| 335 | +Yes. The `@openpanel/nuxt` module supports both Nuxt 3 and Nuxt 4. It uses Nuxt's module system and auto-imports, so everything works seamlessly with either version. |
| 336 | +</FaqItem> |
| 337 | + |
| 338 | +<FaqItem question="Is the useOpenPanel composable auto-imported?"> |
| 339 | +Yes. The module automatically registers the `useOpenPanel` composable, so you can use it in any component without importing it. You can also access the instance via `useNuxtApp().$openpanel`. |
| 340 | +</FaqItem> |
| 341 | + |
| 342 | +<FaqItem question="Does OpenPanel use cookies?"> |
| 343 | +No. OpenPanel uses cookieless tracking by default. This means you don't need cookie consent banners for basic analytics under most privacy regulations, including GDPR and PECR. |
| 344 | +</FaqItem> |
| 345 | + |
| 346 | +<FaqItem question="How do I avoid ad blockers?"> |
| 347 | +Enable the `proxy: true` option in your configuration. This routes all tracking requests through your Nuxt server at `/api/openpanel/*`, which ad blockers don't typically block. |
| 348 | +</FaqItem> |
| 349 | + |
| 350 | +<FaqItem question="Is OpenPanel GDPR compliant?"> |
| 351 | +Yes. OpenPanel is designed for GDPR compliance with cookieless tracking, data minimization, and full support for data subject rights. With self-hosting, you also eliminate international data transfer concerns entirely. |
| 352 | +</FaqItem> |
| 353 | +</Faqs> |
| 354 | + |
0 commit comments