Frameworks personalizados
Integre o Zenovay analytics com frameworks personalizados, ferramentas de build e arquiteturas únicas. Este guia cobre padrões para qualquer configuração não contemplada pelas nossas integrações oficiais.
Referência rápida
| Tipo de integração | Complexidade | Melhor para |
|---|---|---|
| Tag de script | Simples | Sites estáticos, configurações básicas |
| API JavaScript | Média | SPAs, roteamento dinâmico |
| API HTTP | Avançado | Servidor, headless, IoT |
| Proxy próprio | Avançado | Bypass de bloqueadores de anúncios |
Métodos principais de integração
Método 1: Tag de script (O mais simples)
Adicione o script de rastreamento a qualquer página HTML:
<!DOCTYPE html>
<html>
<head>
<title>Seu Site</title>
<!-- Zenovay Analytics -->
<script defer data-tracking-code="YOUR_TRACKING_CODE" src="https://api.zenovay.com/z.js"></script>
</head>
<body>
<!-- Seu conteúdo -->
</body>
</html>
Atributos:
| Atributo | Obrigatório | Descrição |
|---|---|---|
data-tracking-code | Sim | Seu código de rastreamento |
defer | Recomendado | Carregamento não bloqueante |
data-api-url | Não | URL do proxy próprio |
data-auto | Não | Defina como false para desativar as visualizações de página automáticas |
Método 2: API JavaScript
Para controle programático:
// Função de fila (funciona antes do carregamento do script)
window.zenovay = window.zenovay || function() {
(window.zenovay.q = window.zenovay.q || []).push(arguments);
};
// Rastrear visualizações de página manualmente
zenovay('page');
// Rastrear eventos personalizados
zenovay('track', 'button_click', {
button_name: 'signup',
location: 'header'
});
// Identificar usuários
zenovay('identify', 'user_123', {
email: '[email protected]',
plan: 'pro'
});
Método 3: API HTTP
Para ambientes no servidor ou fora do navegador:
// Exemplo Node.js
const response = await fetch('https://api.zenovay.com/e/YOUR_TRACKING_CODE', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Forwarded-For': clientIp // Importante para geolocalização
},
body: JSON.stringify({
event: 'pageview',
url: 'https://yoursite.com/page',
referrer: 'https://google.com',
user_agent: clientUserAgent,
timestamp: new Date().toISOString()
})
});
Veja Endpoints da API para a documentação completa da API.
Aplicações de página única (SPAs)
As SPAs requerem rastreamento manual de visualizações de página nas mudanças de rota.
Padrão genérico para SPA
// Criar um wrapper de rastreamento
class ZenovayTracker {
constructor(siteId) {
this.siteId = siteId;
this.previousPath = null;
}
init() {
// Função de fila (funciona antes do carregamento do script)
window.zenovay = window.zenovay || function() {
(window.zenovay.q = window.zenovay.q || []).push(arguments);
};
}
trackPageView(path) {
// Evitar rastreamento duplicado
if (path === this.previousPath) return;
this.previousPath = path;
zenovay('page');
}
trackEvent(name, properties = {}) {
zenovay('track', name, properties);
}
}
// Uso
const tracker = new ZenovayTracker('YOUR_TRACKING_CODE');
tracker.init();
// Na mudança de rota
router.on('change', (route) => {
tracker.trackPageView(route.path);
});
Integração com a History API
Para SPAs que usam a History API:
// Interceptar pushState e 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'));
};
// Também tratar popstate (voltar/avançar)
window.addEventListener('popstate', () => {
window.dispatchEvent(new Event('locationchange'));
});
// Rastrear na mudança de localização
window.addEventListener('locationchange', () => {
zenovay('page');
});
Roteamento baseado em hash
Para apps que usam roteamento por hash:
window.addEventListener('hashchange', () => {
zenovay('page');
});
// Visualização de página inicial
document.addEventListener('DOMContentLoaded', () => {
zenovay('page');
});
Geradores de sites estáticos
Eleventy (11ty)
Crie _includes/analytics.njk:
<!-- Zenovay Analytics -->
<script defer data-tracking-code="{{ site.zenovayId }}" src="https://api.zenovay.com/z.js"></script>
Adicione ao seu template base:
<!DOCTYPE html>
<html>
<head>
{% include "analytics.njk" %}
</head>
<body>
{{ content | safe }}
</body>
</html>
Em _data/site.js:
module.exports = {
zenovayId: process.env.ZENOVAY_TRACKING_CODE || 'YOUR_TRACKING_CODE'
};
Hugo
Crie 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 }}
Em config.toml:
[params]
zenovayId = "YOUR_TRACKING_CODE"
Inclua em layouts/_default/baseof.html:
<head>
{{ partial "analytics.html" . }}
</head>
Jekyll
Crie _includes/analytics.html:
{% if jekyll.environment == "production" %}
<script defer data-tracking-code="{{ site.zenovay_id }}" src="https://api.zenovay.com/z.js"></script>
{% endif %}
Em _config.yml:
zenovay_id: "YOUR_TRACKING_CODE"
Gatsby
Crie 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"
/>
]);
}
};
Para mudanças de rota, em gatsby-browser.js:
export const onRouteUpdate = ({ location }) => {
if (typeof window.zenovay !== 'undefined') {
window.zenovay('page');
}
};
Remix
Adicione ao 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>
);
}
Progressive Web Apps (PWAs)
Rastreamento de instalação
// Rastrear instalação de PWA
window.addEventListener('beforeinstallprompt', (e) => {
zenovay('track', 'pwa_install_prompt_shown');
});
window.addEventListener('appinstalled', () => {
zenovay('track', 'pwa_installed', {
timestamp: new Date().toISOString()
});
});
Fila de rastreamento offline
Rastreie eventos offline e envie quando voltar online:
class OfflineQueue {
constructor() {
this.storageKey = 'zenovay_offline_queue';
this.init();
}
init() {
// Enviar eventos enfileirados ao ficar online
window.addEventListener('online', () => this.flush());
// Verificar se precisa enviar ao carregar
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; // Parar na primeira falha
}
}
// Remover itens enviados com sucesso
const remaining = queue.filter(item => !successful.includes(item));
this.saveQueue(remaining);
}
}
// Uso
const offlineQueue = new OfflineQueue();
// Rastrear evento (funciona offline)
offlineQueue.add('page_view', { path: '/home' });
Integração com Service Worker
No seu 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 {
// Tentará novamente na próxima sincronização
}
}
}
Web Components
Elemento personalizado com rastreamento
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);
Uso:
<trackable-button
track-event="cta_click"
track-properties='{"location": "hero", "variant": "primary"}'>
Começar
</trackable-button>
Considerações sobre Shadow DOM
Ao usar o Shadow DOM, anexe ouvintes de eventos ao shadow root:
class TrackedComponent extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
}
connectedCallback() {
this.render();
this.setupTracking();
}
render() {
// Criar elemento de botão de forma segura
const button = document.createElement('button');
button.id = 'cta';
button.textContent = 'Clique aqui';
this.shadowRoot.appendChild(button);
}
setupTracking() {
// Rastrear cliques dentro do Shadow DOM
this.shadowRoot.addEventListener('click', (e) => {
if (e.target.id === 'cta') {
zenovay('track', 'shadow_button_click', {
component: 'tracked-component'
});
}
});
// Rastrear visibilidade com 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);
}
}
Integrações com ferramentas de build
Plugin Vite
Crie 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>`);
}
};
}
// Uso em 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
Crie 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;
// Uso em 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>`);
}
}
}
}
};
}
Rastreamento no servidor
Padrão de 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) {
// Reenfileirar em caso de falha
this.queue.unshift(...events);
console.error('Zenovay: Failed to send events', error);
}
}
// Middleware Express
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;
// Uso
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');
});
Exemplo em 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()
})
# Exemplo com Flask
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'}
Testes e depuração
Modo debug
// Ativar modo debug
zenovay('debug');
// Todos os eventos serão registrados no console
zenovay('track', 'test_event', { test: true });
// Console: [Zenovay] Event tracked: test_event { test: true }
Rastreador mock para testes
// __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;
// Em seu teste
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' }]
);
});
Inspeção de rede
Verifique se o rastreamento está funcionando:
- Abra o DevTools → aba Network
- Filtre por
zenovayouz.js - Clique pelo seu site
- Confirme as requisições para
api.zenovay.com
Política de segurança de conteúdo
Configure os cabeçalhos CSP para permitir o Zenovay:
Content-Security-Policy:
default-src 'self';
script-src 'self' https://api.zenovay.com;
connect-src 'self' https://api.zenovay.com;
Para rastreamento próprio (recomendado):
Content-Security-Policy:
default-src 'self';
script-src 'self';
connect-src 'self';
Boas práticas
- Carregue de forma assíncrona: sempre use
deferouasyncpara evitar bloqueios - Trate erros: encapsule as chamadas de rastreamento em try-catch para maior resiliência
- Respeite a privacidade: verifique o consentimento antes de rastrear, respeite o DNT
- Agrupe eventos: combine eventos quando possível para reduzir requisições
- Teste rigorosamente: verifique o rastreamento em múltiplos navegadores e dispositivos
- Use rastreamento próprio: configure o rastreamento próprio para maior precisão
- Monitore o desempenho: certifique-se de que o rastreamento não impacta os Core Web Vitals
Recursos relacionados
- Script de rastreamento
- Eventos personalizados
- Rastreamento próprio
- Endpoints da API
- Exemplo Vanilla JS
Precisa de ajuda com sua integração? Entre em contato com [email protected].