Frameworks personnalisés
Intégrez Zenovay Analytics avec des frameworks personnalisés, des outils de compilation et des architectures uniques. Ce guide couvre les modèles pour toute configuration non couverte par nos intégrations officielles.
Référence rapide
| Type d'intégration | Complexité | Idéale pour |
|---|---|---|
| Balise script | Simple | Sites statiques, configurations de base |
| API JavaScript | Moyenne | SPA, routage dynamique |
| API HTTP | Avancée | Côté serveur, headless, IoT |
| Proxy first-party | Avancée | Contournement des bloqueurs de publicité |
Méthodes d'intégration principales
Méthode 1 : Balise script (la plus simple)
Ajoutez le script de suivi à n'importe quelle page HTML :
<!DOCTYPE html>
<html>
<head>
<title>Your Site</title>
<!-- Zenovay Analytics -->
<script defer data-tracking-code="YOUR_TRACKING_CODE" src="https://api.zenovay.com/z.js"></script>
</head>
<body>
<!-- Your content -->
</body>
</html>
Attributs :
| Attribut | Requis | Description |
|---|---|---|
data-tracking-code | Oui | Votre code de suivi |
defer | Recommandé | Chargement non bloquant |
data-api-url | Non | URL du proxy first-party |
data-auto | Non | Définissez false pour désactiver les pages vues automatiques |
Méthode 2 : API JavaScript
Pour un contrôle programmatique :
// Queue function (works before script loads)
window.zenovay = window.zenovay || function() {
(window.zenovay.q = window.zenovay.q || []).push(arguments);
};
// Track page views manually
zenovay('page');
// Track custom events
zenovay('track', 'button_click', {
button_name: 'signup',
location: 'header'
});
// Identify users
zenovay('identify', 'user_123', {
email: '[email protected]',
plan: 'pro'
});
Méthode 3 : API HTTP
Pour les environnements côté serveur ou non-navigateur :
// Node.js example
const response = await fetch('https://api.zenovay.com/e/YOUR_TRACKING_CODE', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Forwarded-For': clientIp // Important for geo
},
body: JSON.stringify({
event: 'pageview',
url: 'https://yoursite.com/page',
referrer: 'https://google.com',
user_agent: clientUserAgent,
timestamp: new Date().toISOString()
})
});
Consultez Points de terminaison de l'API pour la documentation complète de l'API.
Applications monopages (SPA)
Les SPA nécessitent un suivi manuel des pages vues lors des changements de route.
Modèle SPA générique
// Create a tracking wrapper
class ZenovayTracker {
constructor(siteId) {
this.siteId = siteId;
this.previousPath = null;
}
init() {
// Queue function (works before script loads)
window.zenovay = window.zenovay || function() {
(window.zenovay.q = window.zenovay.q || []).push(arguments);
};
}
trackPageView(path) {
// Prevent duplicate tracking
if (path === this.previousPath) return;
this.previousPath = path;
zenovay('page');
}
trackEvent(name, properties = {}) {
zenovay('track', name, properties);
}
}
// Usage
const tracker = new ZenovayTracker('YOUR_TRACKING_CODE');
tracker.init();
// On route change
router.on('change', (route) => {
tracker.trackPageView(route.path);
});
Intégration avec l'API History
Pour les SPA utilisant l'API History :
// Intercept pushState and replaceState
const originalPushState = history.pushState;
const originalReplaceState = history.replaceState;
history.pushState = function(...args) {
originalPushState.apply(this, args);
window.dispatchEvent(new Event('locationchange'));
};
history.replaceState = function(...args) {
originalReplaceState.apply(this, args);
window.dispatchEvent(new Event('locationchange'));
};
// Also handle popstate (back/forward)
window.addEventListener('popstate', () => {
window.dispatchEvent(new Event('locationchange'));
});
// Track on location change
window.addEventListener('locationchange', () => {
zenovay('page');
});
Routage basé sur les hash
Pour les applications utilisant le routage par hash :
window.addEventListener('hashchange', () => {
zenovay('page');
});
// Initial page view
document.addEventListener('DOMContentLoaded', () => {
zenovay('page');
});
Générateurs de sites statiques
Eleventy (11ty)
Créez _includes/analytics.njk :
<!-- Zenovay Analytics -->
<script defer data-tracking-code="{{ site.zenovayId }}" src="https://api.zenovay.com/z.js"></script>
Ajoutez à votre template de base :
<!DOCTYPE html>
<html>
<head>
{% include "analytics.njk" %}
</head>
<body>
{{ content | safe }}
</body>
</html>
Dans _data/site.js :
module.exports = {
zenovayId: process.env.ZENOVAY_TRACKING_CODE || 'YOUR_TRACKING_CODE'
};
Hugo
Créez layouts/partials/analytics.html :
{{ if not .Site.IsServer }}
<script defer data-tracking-code="{{ .Site.Params.zenovayId }}" src="https://api.zenovay.com/z.js"></script>
{{ end }}
Dans config.toml :
[params]
zenovayId = "YOUR_TRACKING_CODE"
Incluez dans layouts/_default/baseof.html :
<head>
{{ partial "analytics.html" . }}
</head>
Jekyll
Créez _includes/analytics.html :
{% if jekyll.environment == "production" %}
<script defer data-tracking-code="{{ site.zenovay_id }}" src="https://api.zenovay.com/z.js"></script>
{% endif %}
Dans _config.yml :
zenovay_id: "YOUR_TRACKING_CODE"
Gatsby
Créez gatsby-ssr.js :
import React from "react";
export const onRenderBody = ({ setHeadComponents }) => {
if (process.env.NODE_ENV === "production") {
setHeadComponents([
<script
key="zenovay"
defer
data-tracking-code={process.env.GATSBY_ZENOVAY_ID}
src="https://api.zenovay.com/z.js"
/>
]);
}
};
Pour les changements de route, dans gatsby-browser.js :
export const onRouteUpdate = ({ location }) => {
if (typeof window.zenovay !== 'undefined') {
window.zenovay('page');
}
};
Remix
Ajoutez à app/root.tsx :
import { Scripts } from "@remix-run/react";
export default function App() {
return (
<html>
<head>
{process.env.NODE_ENV === "production" && (
<script
defer
data-tracking-code={process.env.ZENOVAY_ID}
src="https://api.zenovay.com/z.js"
/>
)}
</head>
<body>
<Outlet />
<Scripts />
</body>
</html>
);
}
Applications web progressives (PWA)
Suivi d'installation
// Track PWA installation
window.addEventListener('beforeinstallprompt', (e) => {
zenovay('track', 'pwa_install_prompt_shown');
});
window.addEventListener('appinstalled', () => {
zenovay('track', 'pwa_installed', {
timestamp: new Date().toISOString()
});
});
File d'attente de suivi hors ligne
Suivez les événements hors ligne et envoyez-les lorsque la connexion est rétablie :
class OfflineQueue {
constructor() {
this.storageKey = 'zenovay_offline_queue';
this.init();
}
init() {
// Send queued events when coming online
window.addEventListener('online', () => this.flush());
// Check if we need to flush on load
if (navigator.onLine) {
this.flush();
}
}
getQueue() {
try {
return JSON.parse(localStorage.getItem(this.storageKey)) || [];
} catch {
return [];
}
}
saveQueue(queue) {
localStorage.setItem(this.storageKey, JSON.stringify(queue));
}
add(event, properties) {
const queue = this.getQueue();
queue.push({
event,
properties,
timestamp: new Date().toISOString(),
url: window.location.href
});
this.saveQueue(queue);
if (navigator.onLine) {
this.flush();
}
}
async flush() {
const queue = this.getQueue();
if (queue.length === 0) return;
const successful = [];
for (const item of queue) {
try {
zenovay('track', item.event, {
...item.properties,
_queued_at: item.timestamp,
_sent_at: new Date().toISOString()
});
successful.push(item);
} catch (error) {
console.error('Failed to send queued event:', error);
break; // Stop on first failure
}
}
// Remove successfully sent items
const remaining = queue.filter(item => !successful.includes(item));
this.saveQueue(remaining);
}
}
// Usage
const offlineQueue = new OfflineQueue();
// Track event (works offline)
offlineQueue.add('page_view', { path: '/home' });
Intégration avec le Service Worker
Dans votre service worker :
// sw.js
self.addEventListener('sync', (event) => {
if (event.tag === 'zenovay-sync') {
event.waitUntil(syncAnalytics());
}
});
async function syncAnalytics() {
const cache = await caches.open('zenovay-analytics');
const requests = await cache.keys();
for (const request of requests) {
try {
await fetch(request);
await cache.delete(request);
} catch {
// Will retry on next sync
}
}
}
Web Components
Élément personnalisé avec suivi
class TrackableButton extends HTMLElement {
static get observedAttributes() {
return ['track-event', 'track-properties'];
}
connectedCallback() {
this.addEventListener('click', this.handleClick.bind(this));
}
handleClick() {
const eventName = this.getAttribute('track-event') || 'button_click';
let properties = {};
try {
properties = JSON.parse(this.getAttribute('track-properties') || '{}');
} catch {}
if (window.zenovay) {
zenovay('track', eventName, {
...properties,
element: this.tagName.toLowerCase(),
text: this.textContent
});
}
}
}
customElements.define('trackable-button', TrackableButton);
Utilisation :
<trackable-button
track-event="cta_click"
track-properties='{"location": "hero", "variant": "primary"}'>
Get Started
</trackable-button>
Considérations pour le Shadow DOM
Lorsque vous utilisez le Shadow DOM, attachez les écouteurs d'événements à la shadow root :
class TrackedComponent extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
}
connectedCallback() {
this.render();
this.setupTracking();
}
render() {
// Create button element safely
const button = document.createElement('button');
button.id = 'cta';
button.textContent = 'Click Me';
this.shadowRoot.appendChild(button);
}
setupTracking() {
// Track clicks within shadow DOM
this.shadowRoot.addEventListener('click', (e) => {
if (e.target.id === 'cta') {
zenovay('track', 'shadow_button_click', {
component: 'tracked-component'
});
}
});
// Track visibility with Intersection Observer
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
zenovay('track', 'component_viewed', {
component: 'tracked-component'
});
observer.disconnect();
}
});
});
observer.observe(this);
}
}
Intégrations avec les outils de compilation
Plugin Vite
Créez vite-plugin-zenovay.js :
export default function zenovayPlugin(options = {}) {
const { siteId, enabled = true } = options;
return {
name: 'vite-plugin-zenovay',
transformIndexHtml(html) {
if (!enabled || !siteId) return html;
const script = `<script defer data-tracking-code="${siteId}" src="https://api.zenovay.com/z.js"></script>`;
return html.replace('</head>', `${script}</head>`);
}
};
}
// Usage in vite.config.js
import zenovayPlugin from './vite-plugin-zenovay';
export default {
plugins: [
zenovayPlugin({
siteId: process.env.VITE_ZENOVAY_ID,
enabled: process.env.NODE_ENV === 'production'
})
]
};
Plugin Webpack
Créez webpack-zenovay-plugin.js :
const HtmlWebpackPlugin = require('html-webpack-plugin');
class ZenovayWebpackPlugin {
constructor(options = {}) {
this.options = options;
}
apply(compiler) {
compiler.hooks.compilation.tap('ZenovayPlugin', (compilation) => {
HtmlWebpackPlugin.getHooks(compilation).beforeEmit.tapAsync(
'ZenovayPlugin',
(data, cb) => {
if (this.options.siteId && this.options.enabled !== false) {
const script = `<script defer data-tracking-code="${this.options.siteId}" src="https://api.zenovay.com/z.js"></script>`;
data.html = data.html.replace('</head>', `${script}</head>`);
}
cb(null, data);
}
);
});
}
}
module.exports = ZenovayWebpackPlugin;
// Usage in webpack.config.js
const ZenovayPlugin = require('./webpack-zenovay-plugin');
module.exports = {
plugins: [
new HtmlWebpackPlugin(),
new ZenovayPlugin({
siteId: process.env.ZENOVAY_ID,
enabled: process.env.NODE_ENV === 'production'
})
]
};
Plugin Rollup
// rollup-plugin-zenovay.js
export default function zenovayPlugin(options = {}) {
return {
name: 'zenovay',
generateBundle(outputOptions, bundle) {
for (const fileName in bundle) {
if (fileName.endsWith('.html')) {
const chunk = bundle[fileName];
if (chunk.type === 'asset') {
const script = `<script defer data-tracking-code="${options.siteId}" src="https://api.zenovay.com/z.js"></script>`;
chunk.source = chunk.source.replace('</head>', `${script}</head>`);
}
}
}
}
};
}
Suivi côté serveur
Modèle SDK Node.js
// zenovay-server.js
class ZenovayServer {
constructor(siteId, options = {}) {
this.siteId = siteId;
this.apiUrl = options.apiUrl || 'https://api.zenovay.com/e/';
this.queue = [];
this.flushInterval = options.flushInterval || 5000;
this.batchSize = options.batchSize || 10;
this.startFlushTimer();
}
startFlushTimer() {
setInterval(() => this.flush(), this.flushInterval);
}
track(event, properties = {}, context = {}) {
this.queue.push({
event,
properties,
user_agent: context.userAgent,
ip: context.ip,
url: context.url,
referrer: context.referrer,
timestamp: new Date().toISOString()
});
if (this.queue.length >= this.batchSize) {
this.flush();
}
}
async flush() {
if (this.queue.length === 0) return;
const events = this.queue.splice(0, this.batchSize);
try {
await fetch(this.apiUrl + this.siteId, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ events })
});
} catch (error) {
// Re-queue on failure
this.queue.unshift(...events);
console.error('Zenovay: Failed to send events', error);
}
}
// Express middleware
middleware() {
return (req, res, next) => {
req.trackEvent = (event, properties) => {
this.track(event, properties, {
userAgent: req.headers['user-agent'],
ip: req.ip,
url: req.originalUrl,
referrer: req.headers.referer
});
};
next();
};
}
}
module.exports = ZenovayServer;
// Usage
const ZenovayServer = require('./zenovay-server');
const zenovay = new ZenovayServer('YOUR_TRACKING_CODE');
app.use(zenovay.middleware());
app.get('/checkout', (req, res) => {
req.trackEvent('checkout_started', {
cart_value: 99.99
});
res.render('checkout');
});
Exemple Python
import requests
from datetime import datetime
from threading import Thread
from queue import Queue
class ZenovayTracker:
def __init__(self, tracking_code, api_url='https://api.zenovay.com/e/'):
self.tracking_code = tracking_code
self.api_url = api_url
self.queue = Queue()
self._start_worker()
def _start_worker(self):
def worker():
while True:
event = self.queue.get()
try:
requests.post(self.api_url + self.tracking_code, json=event, timeout=5)
except Exception as e:
print(f"Zenovay error: {e}")
self.queue.task_done()
thread = Thread(target=worker, daemon=True)
thread.start()
def track(self, event, properties=None, context=None):
context = context or {}
self.queue.put({
'event': event,
'properties': properties or {},
'user_agent': context.get('user_agent'),
'ip': context.get('ip'),
'url': context.get('url'),
'timestamp': datetime.utcnow().isoformat()
})
# Flask example
from flask import Flask, request
app = Flask(__name__)
tracker = ZenovayTracker('YOUR_TRACKING_CODE')
@app.route('/purchase', methods=['POST'])
def purchase():
tracker.track('purchase', {
'revenue': request.json.get('total'),
'currency': 'USD'
}, {
'user_agent': request.headers.get('User-Agent'),
'ip': request.remote_addr,
'url': request.url
})
return {'status': 'ok'}
Tests et débogage
Mode débogage
// Enable debug mode
zenovay('debug');
// All events will be logged to console
zenovay('track', 'test_event', { test: true });
// Console: [Zenovay] Event tracked: test_event { test: true }
Tracker simulé pour les tests
// __mocks__/zenovay.js
const mockZenovay = {
calls: [],
handler(...args) {
mockZenovay.calls.push(args);
},
reset() {
mockZenovay.calls = [];
},
getEvents() {
return mockZenovay.calls.filter(c => c[0] === 'track');
}
};
window.zenovay = (...args) => mockZenovay.handler(...args);
window.zenovay.mock = mockZenovay;
// In your test
test('tracks button click', () => {
window.zenovay.mock.reset();
fireEvent.click(getByRole('button', { name: 'Sign Up' }));
expect(window.zenovay.mock.getEvents()).toContainEqual(
['track', 'signup_click', { location: 'header' }]
);
});
Inspection réseau
Vérifiez que le suivi fonctionne :
- Ouvrez DevTools → onglet Réseau
- Filtrez par
zenovayouz.js - Naviguez sur votre site
- Vérifiez les requêtes vers
api.zenovay.com
Politique de sécurité du contenu
Configurez les en-têtes CSP pour autoriser Zenovay :
Content-Security-Policy:
default-src 'self';
script-src 'self' https://api.zenovay.com;
connect-src 'self' https://api.zenovay.com;
Pour le suivi first-party (recommandé) :
Content-Security-Policy:
default-src 'self';
script-src 'self';
connect-src 'self';
Bonnes pratiques
- Chargez de façon asynchrone : Utilisez toujours
deferouasyncpour éviter le blocage - Gérez les erreurs : Encadrez les appels de suivi dans des blocs try-catch pour la résilience
- Respectez la confidentialité : Vérifiez le consentement avant le suivi, respectez DNT
- Regroupez les événements : Combinez les événements lorsque possible pour réduire les requêtes
- Testez rigoureusement : Vérifiez le suivi dans plusieurs navigateurs et appareils
- Utilisez le first-party : Configurez le suivi first-party pour une meilleure précision
- Surveillez les performances : Assurez-vous que le suivi n'impacte pas les Core Web Vitals
Ressources associées
- Script de suivi
- Événements personnalisés
- Suivi first-party
- Points de terminaison de l'API
- Exemple Vanilla JS
Besoin d'aide pour votre intégration ? Contactez [email protected].