Integre o analytics do Zenovay em sua aplicação Next.js com suporte ao App Router e ao Pages Router usando a tag de script de rastreamento.
Instalação
Nenhum pacote npm é necessário. Adicione o script de rastreamento do Zenovay ao seu app usando o componente Script do Next.js ou uma tag <script> padrão.
Você pode encontrar seu código de rastreamento no app Zenovay: abra Domains, clique em seu site e então abra sua página de configuração General. O card Tracking script mostra o snippet e um botão Verify Installation.
Configuração com App Router (Next.js 13+)
Adicionar ao Layout Raiz
// app/layout.tsx
import Script from 'next/script';
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>
{children}
<Script
src="https://api.zenovay.com/z.js"
data-tracking-code="YOUR_TRACKING_CODE"
strategy="afterInteractive"
/>
</body>
</html>
);
}
Com Variável de Ambiente
// app/layout.tsx
import Script from 'next/script';
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>
{children}
<Script
src="https://api.zenovay.com/z.js"
data-tracking-code={process.env.NEXT_PUBLIC_ZENOVAY_TRACKING_CODE}
strategy="afterInteractive"
/>
</body>
</html>
);
}
Componente Client para Eventos
Crie um componente client para rastrear visualizações de página na navegação do lado do cliente (SPA):
// components/Analytics.tsx
'use client';
import { usePathname, useSearchParams } from 'next/navigation';
import { useEffect } from 'react';
export function Analytics() {
const pathname = usePathname();
const searchParams = useSearchParams();
useEffect(() => {
// Track page view on route change (SPA navigation)
if (window.zenovay) {
window.zenovay('page');
}
}, [pathname, searchParams]);
return null;
}
Adicione ao layout:
// app/layout.tsx
import Script from 'next/script';
import { Analytics } from '@/components/Analytics';
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>
{children}
<Analytics />
<Script
src="https://api.zenovay.com/z.js"
data-tracking-code="YOUR_TRACKING_CODE"
strategy="afterInteractive"
/>
</body>
</html>
);
}
Configuração com Pages Router
Adicionar ao _app.tsx
// pages/_app.tsx
import Script from 'next/script';
import type { AppProps } from 'next/app';
export default function App({ Component, pageProps }: AppProps) {
return (
<>
<Component {...pageProps} />
<Script
src="https://api.zenovay.com/z.js"
data-tracking-code="YOUR_TRACKING_CODE"
strategy="afterInteractive"
/>
</>
);
}
Ou use Script no _document.tsx
// pages/_document.tsx
import { Html, Head, Main, NextScript } from 'next/document';
import Script from 'next/script';
export default function Document() {
return (
<Html>
<Head />
<body>
<Main />
<NextScript />
<Script
src="https://api.zenovay.com/z.js"
data-tracking-code="YOUR_TRACKING_CODE"
strategy="afterInteractive"
/>
</body>
</Html>
);
}
Rastreamento de Eventos
Componente Client
'use client';
export function SignupButton() {
const handleClick = () => {
if (window.zenovay) {
window.zenovay('track', 'signup_click', {
plan: 'pro',
source: 'pricing'
});
}
};
return (
<button onClick={handleClick}>
Start Free Trial
</button>
);
}
Identificação de Usuário
Após Autenticação
'use client';
import { useEffect } from 'react';
import { useSession } from 'next-auth/react';
export function UserIdentifier() {
const { data: session } = useSession();
useEffect(() => {
if (session?.user && window.zenovay) {
window.zenovay('identify', session.user.id, {
email: session.user.email,
name: session.user.name
});
}
}, [session]);
return null;
}
Rastreamento de Receita
Checkout de E-commerce
'use client';
import { useEffect } from 'react';
export function OrderConfirmation({ order }) {
useEffect(() => {
if (window.zenovay) {
window.zenovay('revenue', order.total, 'USD', {
order_id: order.id,
items: order.items
});
}
}, [order.id]);
return (
<div>
<h1>Order Confirmed!</h1>
</div>
);
}
Informação
Para compras capturadas pelo seu backend (ex. um webhook do Stripe), informe a receita do servidor em vez disso — veja Server-Side Tracking abaixo. Isso mantém a atribuição de receita precisa mesmo se o navegador nunca alcançar uma página de confirmação.
Rastreamento no Lado do Servidor
Para eventos dos quais seu backend é a fonte da verdade — compras, inscrições, atividade fora do site — envie-os do servidor com o ponto de extremidade POST /api/v1/events. Esta é uma API de ingestão separada e autenticada.
Aviso
A ingestão do lado do servidor requer uma chave de API e um plano pago (Pro, Scale ou Enterprise). Contas gratuitas recebem 403 API_PAID_PLAN_REQUIRED. Crie uma chave em Settings → Security → API keys — veja Getting an API Key. O script de rastreamento do navegador acima permanece grátis em todos os planos.
Autentique-se com uma chave de API zv_* no cabeçalho Authorization: Bearer. Cada evento tem um type (pageview, event, identify, goal ou purchase), um ts em milissegundos e um objeto props específico do tipo. Passe um idempotencyKey para tornar as retentativas seguras.
Server Action
// app/actions.ts
'use server';
export async function submitForm(formData: FormData) {
// Process form
const email = formData.get('email');
// Track a custom event server-side
await fetch('https://api.zenovay.com/api/v1/events', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.ZENOVAY_API_KEY}`,
},
body: JSON.stringify({
trackingCode: process.env.ZENOVAY_TRACKING_CODE,
events: [{
type: 'event',
ts: Date.now(),
props: {
name: 'form_submitted',
form: 'contact',
has_email: !!email,
},
idempotencyKey: `form-${crypto.randomUUID()}`,
}],
}),
});
return { success: true };
}
Route Handler
// app/api/track/route.ts
import { NextRequest, NextResponse } from 'next/server';
export async function POST(request: NextRequest) {
const body = await request.json();
await fetch('https://api.zenovay.com/api/v1/events', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.ZENOVAY_API_KEY}`,
},
body: JSON.stringify({
trackingCode: process.env.ZENOVAY_TRACKING_CODE,
events: [{
type: 'event',
ts: Date.now(),
props: {
name: body.name || 'custom_event',
...body.props,
},
idempotencyKey: crypto.randomUUID(),
}],
serverContext: {
clientIp: request.headers.get('x-forwarded-for') ?? undefined,
userAgent: request.headers.get('user-agent') ?? undefined,
},
})
});
return NextResponse.json({ success: true });
}
Informação
Eventos pageview precisam de um visitorId e sessionId RFC-4122 reais (as colunas são tipadas como UUID), portanto são geralmente melhor deixados para o rastreador do navegador. O ponto de extremidade do servidor é ideal para os tipos event, identify, goal e purchase. Veja Server-Side Tracking para o esquema completo de eventos e motivos de rejeição.
Compra de um Webhook
// app/api/webhooks/stripe/route.ts
import { NextResponse } from 'next/server';
export async function POST(request: Request) {
const event = await request.json();
// ... verify the Stripe signature first ...
await fetch('https://api.zenovay.com/api/v1/events', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.ZENOVAY_API_KEY}`,
},
body: JSON.stringify({
trackingCode: process.env.ZENOVAY_TRACKING_CODE,
events: [{
type: 'purchase',
ts: Date.now(),
// The visitorId you stored against this customer on your side
visitorId: event.data.object.metadata?.visitor_id,
props: {
amount: event.data.object.amount_total / 100,
currency: event.data.object.currency?.toUpperCase() || 'USD',
payment_provider: 'stripe',
},
idempotencyKey: event.id,
}],
})
});
return NextResponse.json({ received: true });
}
Variáveis de Ambiente
Configuração
# .env.local
NEXT_PUBLIC_ZENOVAY_TRACKING_CODE=your-tracking-code # client-side script (public)
ZENOVAY_TRACKING_CODE=your-tracking-code # server-side (same value)
ZENOVAY_API_KEY=zv_your-api-key # server-side only — never expose
Aviso
Apenas o código de rastreamento (usado no atributo data-tracking-code) é seguro expor com o prefixo NEXT_PUBLIC_. Sua chave de API zv_* deve permanecer no lado do servidor — nunca a prefixe com NEXT_PUBLIC_.
Uso
// Client-side (in layout)
<Script
src="https://api.zenovay.com/z.js"
data-tracking-code={process.env.NEXT_PUBLIC_ZENOVAY_TRACKING_CODE}
strategy="afterInteractive"
/>
// Server-side (in API routes or server actions)
const response = await fetch('https://api.zenovay.com/api/v1/events', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.ZENOVAY_API_KEY}`,
},
body: JSON.stringify({
trackingCode: process.env.ZENOVAY_TRACKING_CODE,
events: [{ type: 'event', ts: Date.now(), props: { name: 'custom_event' } }],
}),
});
Analytics por Route Groups
// app/(marketing)/layout.tsx
import Script from 'next/script';
export default function MarketingLayout({ children }) {
return (
<>
{children}
<Script
src="https://api.zenovay.com/z.js"
data-tracking-code="MARKETING_TRACKING_CODE"
strategy="afterInteractive"
/>
</>
);
}
// app/(app)/layout.tsx
import Script from 'next/script';
export default function AppLayout({ children }) {
return (
<>
{children}
<Script
src="https://api.zenovay.com/z.js"
data-tracking-code="APP_TRACKING_CODE"
strategy="afterInteractive"
/>
</>
);
}
Tipos TypeScript
// types/zenovay.d.ts
interface ZenovayFunction {
(command: 'track', name: string, properties?: Record<string, unknown>): void;
(command: 'identify', userId: string, traits?: Record<string, unknown>): void;
(command: 'goal', name: string, properties?: Record<string, unknown>): void;
(command: 'page'): void;
(command: 'revenue', amount: number, currency: string, meta?: Record<string, unknown>): void;
}
declare global {
interface Window {
zenovay: ZenovayFunction;
}
}
export {};
Padrões Comuns
Com Next-Auth
// app/providers.tsx
'use client';
import { SessionProvider } from 'next-auth/react';
export function Providers({ children }) {
return (
<SessionProvider>
{children}
</SessionProvider>
);
}
Páginas ISR/SSG
// app/blog/[slug]/page.tsx
export async function generateStaticParams() {
const posts = await getPosts();
return posts.map((post) => ({ slug: post.slug }));
}
export default async function BlogPost({ params }) {
// Static generation - no server tracking here
// Tracking happens on client via the Zenovay script tag
const post = await getPost(params.slug);
return <Article post={post} />;
}
Solução de Problemas
Script Não Carregando
Verifique:
- Se o script está no layout/document
- Sem interferência de bloqueadores de anúncios
- Se o tracking code está correto
Sem Visualizações de Página
Certifique-se de:
- Que o script carrega após o body
- Que não está em modo de desenvolvimento (a menos que intencional)
- Que as mudanças de rota são detectadas
Problemas de Hidratação
Use a estratégia afterInteractive:
<Script
src="https://api.zenovay.com/z.js"
data-tracking-code="YOUR_TRACKING_CODE"
strategy="afterInteractive"
/>