Skip to main content
6 min read

Add Zenovay Analytics to Nuxt

Zenovay Analytics pairs naturally with Nuxt, giving Vue developers a fast, privacy-respecting way to track visitors across server-rendered, statically generated, and hybrid Nuxt applications. The integration takes about two minutes: add the tracking script through nuxt.config.ts or as a client-side plugin, and Zenovay immediately begins recording page views, referrers, browser data, and geographic insights in real time. The tracker weighs under 1 KB, loads asynchronously to protect your Core Web Vitals, and automatically picks up client-side route changes through Vue Router. This guide covers both the config-based approach and the plugin approach, plus custom event tracking with composables, user identification, cookieless mode, and server-side rendering considerations specific to 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 and copy your tracking code from Settings → Tracking Code.

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 a server-side hash of the visitor's IP subnet, user agent, and a daily-rotating salt to count unique visitors without storing anything on the client device.

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?