Vue Integration Example
This guide provides a complete, production-ready example of integrating Zenovay analytics into a Vue 3 application using the Composition API and modern Vue patterns.
Quick Start
Add the Zenovay tracking script to your index.html:
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>My Vue App</title>
</head>
<body>
<div id="app"></div>
<script defer data-tracking-code="YOUR_TRACKING_CODE" src="https://api.zenovay.com/z.js"></script>
<script type="module" src="/src/main.js"></script>
</body>
</html>
That's it for basic page view tracking. The script automatically tracks page views on load.
Basic Setup
1. Create a Zenovay Composable
Create a reusable composable for tracking throughout your Vue app:
// composables/useZenovay.js
export function useZenovay() {
const track = (eventName, eventData = {}) => {
if (window.zenovay) {
window.zenovay('track', eventName, eventData);
}
};
const identify = (userId, traits = {}) => {
if (window.zenovay) {
window.zenovay('identify', userId, traits);
}
};
const trackGoal = (goalName, data = {}) => {
if (window.zenovay) {
window.zenovay('goal', goalName, data);
}
};
const trackPurchase = (data) => {
if (window.zenovay) {
window.zenovay('revenue', data.amount, data.currency);
}
};
return {
track,
identify,
trackGoal,
trackPurchase,
};
}
2. Track Page Views (Vue Router)
For single-page applications using Vue Router, track navigation changes:
// router/index.js
import { createRouter, createWebHistory } from 'vue-router';
const router = createRouter({
history: createWebHistory(),
routes: [
// your routes
]
});
router.afterEach((to) => {
// The Zenovay script automatically tracks the initial page load.
// For SPA route changes, you can trigger a page view manually:
if (window.zenovay) {
window.zenovay('track', 'pageview', {
path: to.fullPath,
title: document.title,
});
}
});
export default router;
3. Track Custom Events
<script setup>
import { useZenovay } from '@/composables/useZenovay';
const { track } = useZenovay();
const handleSignup = async () => {
track('signup_started', {
plan: 'professional',
source: 'pricing_page',
});
// Continue with signup logic
};
</script>
<template>
<button @click="handleSignup">Start Free Trial</button>
</template>
Complete Example
Here's a full implementation with all common use cases:
Composable for Page Tracking
// composables/usePageTracking.js
import { watch } from 'vue';
import { useRoute } from 'vue-router';
export function usePageTracking() {
const route = useRoute();
watch(
() => route.fullPath,
() => {
if (window.zenovay) {
window.zenovay('track', 'pageview', {
path: route.fullPath,
referrer: document.referrer,
title: document.title,
});
}
},
{ immediate: true }
);
}
Trackable Button Component
<!-- components/TrackableButton.vue -->
<script setup>
import { useZenovay } from '@/composables/useZenovay';
const props = defineProps({
eventName: {
type: String,
required: true
},
eventData: {
type: Object,
default: () => ({})
}
});
const emit = defineEmits(['click']);
const { track } = useZenovay();
const handleClick = (event) => {
// Track the event
track(props.eventName, props.eventData);
// Emit click event for parent handling
emit('click', event);
};
</script>
<template>
<button @click="handleClick">
<slot />
</button>
</template>
Usage Example
<!-- pages/Pricing.vue -->
<script setup>
import TrackableButton from '@/components/TrackableButton.vue';
</script>
<template>
<div>
<h1>Pricing</h1>
<TrackableButton
event-name="plan_selected"
:event-data="{
plan: 'scale',
billing: 'monthly',
price: 90
}"
class="btn-primary"
>
Choose Scale
</TrackableButton>
</div>
</template>
Advanced Patterns
Conditional Tracking
Track events only when certain conditions are met:
<script setup>
import { watchEffect } from 'vue';
import { useZenovay } from '@/composables/useZenovay';
const props = defineProps(['product']);
const { track } = useZenovay();
watchEffect(() => {
// Only track if product is high-value
if (props.product && props.product.price > 1000) {
track('high_value_product_viewed', {
product_id: props.product.id,
product_name: props.product.name,
price: props.product.price
});
}
});
</script>
<template>
<div><!-- product details --></div>
</template>
Form Tracking Composable
// composables/useFormTracking.js
import { ref } from 'vue';
import { useZenovay } from '@/composables/useZenovay';
export function useFormTracking(formId) {
const { track } = useZenovay();
const formData = ref({});
const trackFieldFocus = (fieldName) => {
track('form_field_focused', {
form_id: formId,
field_name: fieldName
});
};
const trackFormSubmit = (success = true) => {
track('form_submitted', {
form_id: formId,
success,
field_count: Object.keys(formData.value).length
});
};
return {
formData,
trackFieldFocus,
trackFormSubmit
};
}
Form Component
<script setup>
import { useFormTracking } from '@/composables/useFormTracking';
const { formData, trackFieldFocus, trackFormSubmit } = useFormTracking('contact');
const handleSubmit = async () => {
try {
// Submit form logic
await submitContactForm(formData.value);
trackFormSubmit(true);
} catch (error) {
trackFormSubmit(false);
}
};
</script>
<template>
<form @submit.prevent="handleSubmit">
<input
v-model="formData.name"
type="text"
placeholder="Name"
@focus="trackFieldFocus('name')"
/>
<input
v-model="formData.email"
type="email"
placeholder="Email"
@focus="trackFieldFocus('email')"
/>
<textarea
v-model="formData.message"
placeholder="Message"
@focus="trackFieldFocus('message')"
/>
<button type="submit">Send Message</button>
</form>
</template>
E-commerce Tracking Composable
// composables/useEcommerceTracking.js
import { useZenovay } from '@/composables/useZenovay';
export function useEcommerceTracking() {
const { track, trackPurchase } = useZenovay();
const trackProductView = (product) => {
track('product_viewed', {
product_id: product.id,
product_name: product.name,
category: product.category,
price: product.price,
currency: 'USD'
});
};
const trackAddToCart = (product, quantity = 1) => {
track('product_added_to_cart', {
product_id: product.id,
product_name: product.name,
quantity,
price: product.price,
total: product.price * quantity
});
};
const trackRemoveFromCart = (product) => {
track('product_removed_from_cart', {
product_id: product.id,
product_name: product.name
});
};
const trackCheckout = (cart) => {
track('checkout_started', {
cart_total: cart.total,
item_count: cart.items.length,
currency: 'USD'
});
};
const trackPurchaseCompleted = (order) => {
trackPurchase({
amount: order.total,
currency: 'USD',
product: `Order #${order.id}`,
});
// Also track a detailed custom event
track('purchase_completed', {
order_id: order.id,
revenue: order.total,
tax: order.tax,
shipping: order.shipping,
currency: 'USD',
items: order.items.map(item => ({
product_id: item.id,
quantity: item.quantity,
price: item.price
}))
});
};
return {
trackProductView,
trackAddToCart,
trackRemoveFromCart,
trackCheckout,
trackPurchaseCompleted
};
}
E-commerce Page Example
<script setup>
import { onMounted } from 'vue';
import { useEcommerceTracking } from '@/composables/useEcommerceTracking';
const props = defineProps(['product']);
const {
trackProductView,
trackAddToCart
} = useEcommerceTracking();
onMounted(() => {
trackProductView(props.product);
});
const handleAddToCart = () => {
trackAddToCart(props.product);
// Add to cart logic
};
</script>
<template>
<div>
<h1>{{ product.name }}</h1>
<p>${{ product.price }}</p>
<button @click="handleAddToCart">Add to Cart</button>
</div>
</template>
Error Tracking Plugin
// plugins/errorTracking.js
export default {
install(app) {
// Global error handler
app.config.errorHandler = (error, instance, info) => {
if (window.zenovay) {
window.zenovay('track', 'error_occurred', {
error_message: error.message,
error_stack: error.stack,
component_name: instance?.$options?.name,
lifecycle_hook: info,
error_type: 'vue_error'
});
}
console.error('Vue error:', error, info);
};
// Global warning handler (development only)
if (import.meta.env.DEV) {
app.config.warnHandler = (msg, instance, trace) => {
if (window.zenovay) {
window.zenovay('track', 'warning_occurred', {
warning_message: msg,
component_name: instance?.$options?.name,
component_trace: trace
});
}
};
}
}
};
User Identification
<!-- components/UserIdentifier.vue -->
<script setup>
import { watch } from 'vue';
import { useZenovay } from '@/composables/useZenovay';
import { useAuthStore } from '@/stores/auth';
const { identify } = useZenovay();
const authStore = useAuthStore();
watch(
() => authStore.user,
(user) => {
if (user) {
identify(user.id, {
email: user.email,
name: user.name,
plan: user.subscription?.plan,
signup_date: user.createdAt
});
}
},
{ immediate: true }
);
</script>
<template>
<!-- This component doesn't render anything -->
<div style="display: none" />
</template>
Global Directive for Click Tracking
// directives/track.js
export default {
install(app) {
app.directive('track', {
mounted(el, binding) {
el.addEventListener('click', () => {
const [eventName, eventData] = Array.isArray(binding.value)
? binding.value
: [binding.value, {}];
if (window.zenovay) {
window.zenovay('track', eventName, eventData);
}
});
}
});
}
};
Usage with Directive
<template>
<!-- Simple event tracking -->
<button v-track="'button_clicked'">
Click Me
</button>
<!-- With event data -->
<button v-track="['plan_selected', { plan: 'professional' }]">
Choose Plan
</button>
</template>
TypeScript Support
Full TypeScript support with typed events:
// types/analytics.ts
export interface AnalyticsEvents {
signup_started: {
plan: 'free' | 'pro' | 'scale' | 'enterprise';
source: string;
};
product_viewed: {
product_id: string;
product_name: string;
price: number;
};
purchase_completed: {
order_id: string;
revenue: number;
currency: string;
};
}
// composables/useZenovay.ts
// Declare the global zenovay object for TypeScript
declare global {
interface Window {
zenovay: (...args: any[]) => void;
}
}
import type { AnalyticsEvents } from '@/types/analytics';
export function useZenovay() {
const track = <K extends keyof AnalyticsEvents>(
eventName: K,
data: AnalyticsEvents[K]
) => {
if (window.zenovay) {
window.zenovay('track', eventName, data);
}
};
const identify = (userId: string, traits: Record<string, any> = {}) => {
if (window.zenovay) {
window.zenovay('identify', userId, traits);
}
};
const trackGoal = (goalName: string, data: Record<string, any> = {}) => {
if (window.zenovay) {
window.zenovay('goal', goalName, data);
}
};
return {
track,
identify,
trackGoal,
};
}
Pinia Store Integration
Integrate analytics with Pinia state management:
// stores/analytics.js
import { defineStore } from 'pinia';
export const useAnalyticsStore = defineStore('analytics', () => {
const track = (eventName, eventData = {}) => {
try {
if (window.zenovay) {
window.zenovay('track', eventName, eventData);
}
} catch (error) {
console.error('Analytics tracking error:', error);
}
};
const identify = (userId, traits = {}) => {
if (window.zenovay) {
window.zenovay('identify', userId, traits);
}
};
return {
track,
identify,
};
});
Performance Optimization
Debounced Tracking
<script setup>
import { ref } from 'vue';
import { useDebounceFn } from '@vueuse/core';
import { useZenovay } from '@/composables/useZenovay';
const searchQuery = ref('');
const { track } = useZenovay();
const trackSearch = useDebounceFn((query) => {
track('search_performed', {
query,
length: query.length
});
}, 500);
const handleSearch = (event) => {
searchQuery.value = event.target.value;
trackSearch(searchQuery.value);
};
</script>
<template>
<input
v-model="searchQuery"
type="search"
placeholder="Search..."
@input="handleSearch"
/>
</template>
Testing
Mock Zenovay in Tests
// test/setup.js
// Mock the global zenovay object before tests
beforeEach(() => {
window.zenovay = vi.fn();
});
Component Test
import { mount } from '@vue/test-utils';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import TrackableButton from '@/components/TrackableButton.vue';
describe('TrackableButton', () => {
beforeEach(() => {
window.zenovay = vi.fn();
});
it('tracks event on click', async () => {
const wrapper = mount(TrackableButton, {
props: {
eventName: 'button_clicked',
eventData: { id: '123' }
},
slots: {
default: 'Click Me'
}
});
await wrapper.find('button').trigger('click');
expect(window.zenovay).toHaveBeenCalledWith('track', 'button_clicked', { id: '123' });
});
});
Nuxt 3 Integration
Complete integration with Nuxt 3:
// plugins/zenovay.client.js
import { defineNuxtPlugin } from '#app';
export default defineNuxtPlugin(() => {
// Add the tracking script dynamically
if (typeof document !== 'undefined') {
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);
}
});
Nuxt Page Tracking
<!-- app.vue -->
<script setup>
import { watch } from 'vue';
import { useRoute } from 'vue-router';
const route = useRoute();
watch(
() => route.fullPath,
() => {
if (window.zenovay) {
window.zenovay('track', 'pageview', {
path: route.fullPath,
title: document.title,
});
}
},
{ immediate: true }
);
</script>
<template>
<NuxtPage />
</template>
Nuxt Config
// nuxt.config.ts
export default defineNuxtConfig({
app: {
head: {
script: [
{
defer: true,
'data-tracking-code': 'YOUR_TRACKING_CODE',
src: 'https://api.zenovay.com/z.js',
},
],
},
},
});
Alternatively, if you prefer not to use the Nuxt plugin, you can add the script directly to your nuxt.config.ts head configuration as shown above. Both approaches work equally well.
Environment Variables
# .env
# No special environment variables needed for Zenovay!
# The tracking code is embedded directly in the script tag.
# Simply replace YOUR_TRACKING_CODE with your actual tracking code
# from the Zenovay dashboard (app.zenovay.com).