Saltar al contenido principal
10 min de lectura

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).

Additional Resources

¿Fue útil esta página?