Skip to main content
7 min read

Add Zenovay Analytics to Nuxt

This guide walks through adding Zenovay Analytics to a Nuxt 3 app. Add the tracking script through nuxt.config.ts or a client-side plugin, and Zenovay starts recording page views, referrers, browser data, and geographic insights in real time. The tracker is under 1 KB, loads asynchronously, and automatically picks up client-side route changes through Vue Router. The guide covers both setup approaches plus custom event tracking with composables, user identification, cookieless mode, and SSR considerations for Nuxt 3.


Quick Setup (2 minutes)

There are two ways to add Zenovay to Nuxt. Pick whichever fits your project best.

Step 1: Get Your Tracking Code

Sign in to your Zenovay dashboard, open Domains → your site, and copy your tracking code from the General settings page.

Tracking script card showing the script snippet with HTML, React, Next.js, and First-Party tabs and a Verify Installation button
The tracking script card on your site's General settings page. Copy the script tag or use one of the framework tabs.

The simplest approach. Add the script to app.head in your Nuxt config:

nuxt.config.tsTypeScript
export default defineNuxtConfig({
app: {
  head: {
    script: [
      {
        defer: true,
        'data-tracking-code': 'YOUR_TRACKING_CODE',
        src: 'https://api.zenovay.com/z.js',
      },
    ],
  },
},
})

That's it! Zenovay will automatically track page views across all your Nuxt routes, including client-side navigations.

Option B: Client-Side Plugin

If you prefer more control, create a Nuxt plugin that loads the script on the client:

plugins/zenovay.client.tsTypeScript
export default defineNuxtPlugin(() => {
if (typeof document === 'undefined') return;

const script = document.createElement('script');
script.defer = true;
script.dataset.trackingCode = 'YOUR_TRACKING_CODE';
script.src = 'https://api.zenovay.com/z.js';
document.head.appendChild(script);
})

The .client.ts suffix ensures this plugin only runs in the browser, never during SSR.

Step 2: Verify Installation

Start your dev server with npx nuxi dev, visit a page, and check the Zenovay dashboard. You should see a real-time visitor within seconds.

SPA Navigation Tracking

Nuxt uses Vue Router for client-side routing. Zenovay automatically detects route changes via the History API (pushState / popstate), so every navigation records a new page view with zero configuration.

This works with:

  • <NuxtLink> components
  • navigateTo() programmatic navigation
  • useRouter().push() calls
  • Browser back/forward buttons

Custom Events with Composables

Create a reusable composable for clean analytics calls throughout your app:

composables/useZenovay.tsTypeScript
export function useZenovay() {
function track(event: string, data?: Record<string, any>) {
  if (import.meta.client && window.zenovay) {
    window.zenovay('track', event, data);
  }
}

function identify(userId: string, traits?: Record<string, any>) {
  if (import.meta.client && window.zenovay) {
    window.zenovay('identify', userId, traits);
  }
}

function trackGoal(goalName: string, data?: Record<string, any>) {
  if (import.meta.client && window.zenovay) {
    window.zenovay('goal', goalName, data);
  }
}

function trackRevenue(amount: number, currency = 'USD') {
  if (import.meta.client && window.zenovay) {
    window.zenovay('revenue', amount, currency);
  }
}

return { track, identify, trackGoal, trackRevenue };
}

Nuxt auto-imports composables from the composables/ directory, so you can use it anywhere without an import statement:

pages/pricing.vueVUE
<script setup lang="ts">
const { track } = useZenovay();

function handleUpgrade(plan: string) {
track('upgrade_clicked', { plan, location: 'pricing_page' });
navigateTo('/checkout?plan=' + plan);
}
</script>

<template>
<div>
  <h1>Pricing</h1>
  <button @click="handleUpgrade('pro')">Upgrade to Pro</button>
  <button @click="handleUpgrade('scale')">Upgrade to Scale</button>
</div>
</template>

Track Events in Components

components/ContactForm.vueVUE
<script setup lang="ts">
const { track } = useZenovay();

const form = reactive({ name: '', email: '', message: '' });

async function handleSubmit() {
track('form_submitted', {
  form: 'contact',
  has_message: form.message.length > 0,
});

await $fetch('/api/contact', { method: 'POST', body: form });
}
</script>

<template>
<form @submit.prevent="handleSubmit">
  <input v-model="form.name" placeholder="Name" required />
  <input v-model="form.email" type="email" placeholder="Email" required />
  <textarea v-model="form.message" placeholder="Message"></textarea>
  <button type="submit">Send</button>
</form>
</template>

Server-Side Rendering Considerations

Nuxt 3 renders pages on the server by default. The Zenovay script runs entirely on the client, so keep these rules in mind:

  • Never call window.zenovay in server middleware, API routes, or server/ files
  • Always guard client-side calls with import.meta.client or process.client
  • The nuxt.config.ts head script is safe because the browser handles script loading
  • Use .client.ts suffix for plugins that need the window object
SSR safetyTypeScript
// ✅ Safe: guarded with import.meta.client
if (import.meta.client && window.zenovay) {
window.zenovay('track', 'page_view');
}

// ✅ Safe: inside onMounted (client-only lifecycle hook)
onMounted(() => {
if (window.zenovay) {
  window.zenovay('track', 'component_visible');
}
});

// ❌ Unsafe: runs during SSR — will throw!
// window.zenovay('track', 'page_view');

Route Middleware Tracking

Track specific route transitions with Nuxt route middleware:

middleware/analytics.global.tsTypeScript
export default defineNuxtRouteMiddleware((to, from) => {
// Only runs on client-side navigations
if (import.meta.client && window.zenovay) {
  window.zenovay('track', 'route_change', {
    from: from.fullPath,
    to: to.fullPath,
  });
}
})

This is optional. Zenovay auto-tracks page views on every navigation. Use route middleware only if you need custom data attached to specific transitions.

Cookieless Mode

For privacy-friendly tracking without cookies or localStorage, add the data-cookieless attribute:

nuxt.config.ts (cookieless)TypeScript
export default defineNuxtConfig({
app: {
  head: {
    script: [
      {
        defer: true,
        'data-tracking-code': 'YOUR_TRACKING_CODE',
        'data-cookieless': 'true',
        src: 'https://api.zenovay.com/z.js',
      },
    ],
  },
},
})

In cookieless mode, Zenovay uses an in-memory, window-scoped visitor ID that exists only for the current page session. No cookies, no localStorage, and no data is written to the client device. The ID is discarded when the page unloads.

Identify Users

Associate analytics data with authenticated users. A good place is in your auth plugin or layout:

layouts/default.vueVUE
<script setup lang="ts">
const { identify } = useZenovay();
const { data: user } = await useFetch('/api/auth/user');

watch(user, (u) => {
if (u) {
  identify(u.id, {
    email: u.email,
    plan: u.plan,
  });
}
}, { immediate: true });
</script>

<template>
<div>
  <NuxtPage />
</div>
</template>

Track Goals and Revenue

Goal and revenue trackingTypeScript
const { trackGoal, trackRevenue } = useZenovay();

// Track a conversion goal
trackGoal('newsletter_signup', { source: 'footer' });

// Track a purchase
trackRevenue(49.99, 'USD');

TypeScript Support

Add type declarations for the global zenovay function. Nuxt auto-includes *.d.ts files from the project root:

types/zenovay.d.tsTypeScript
declare global {
interface Window {
  zenovay?: (...args: any[]) => void;
}
}

export {};

Environment-Based Configuration

Use runtime config to swap tracking codes between environments:

nuxt.config.ts (with runtime config)TypeScript
export default defineNuxtConfig({
runtimeConfig: {
  public: {
    zenovayTrackingCode: 'YOUR_TRACKING_CODE',
  },
},
})
plugins/zenovay.client.ts (dynamic)TypeScript
export default defineNuxtPlugin(() => {
const config = useRuntimeConfig();
const trackingCode = config.public.zenovayTrackingCode;

if (!trackingCode || typeof document === 'undefined') return;

const script = document.createElement('script');
script.defer = true;
script.dataset.trackingCode = trackingCode;
script.src = 'https://api.zenovay.com/z.js';
document.head.appendChild(script);
})

Then set NUXT_PUBLIC_ZENOVAY_TRACKING_CODE in your environment to override the default.

Next Steps

Your Nuxt app is now tracking with Zenovay! View your analytics in the dashboard.

Continue learning:

Was this page helpful?