Integre el análisis de Zenovay en su aplicación Next.js con soporte para App Router y Pages Router mediante la etiqueta del script de rastreo.
Instalación
No se necesita ningún paquete npm. Agregue el script de rastreo de Zenovay a su aplicación usando el componente Script de Next.js o una etiqueta <script> estándar.
Puede encontrar su código de rastreo en la aplicación Zenovay: abra Domains, haga clic en su sitio y luego abra su página de configuración General. La tarjeta Tracking script muestra el fragmento y un botón Verify Installation.
Configuración con App Router (Next.js 13+)
Agregar al layout raíz
// 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>
);
}
Con variable de entorno
// 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 cliente para eventos
Cree un componente cliente para rastrear vistas de página en la navegación del lado del 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;
}
Agrégalo al 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>
);
}
Configuración con Pages Router
Agregar a _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"
/>
</>
);
}
O usar Script en _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>
);
}
Rastreo de eventos
Componente cliente
'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>
);
}
Identificación de usuarios
Después de la autenticación
'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;
}
Rastreo de ingresos
Confirmación de pedido en 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>
);
}
Información
Para las compras capturadas por su backend (p. ej. un webhook de Stripe), informe los ingresos desde el servidor en su lugar — vea Server-Side Tracking a continuación. Esto mantiene la atribución de ingresos precisa incluso si el navegador nunca llega a una página de confirmación.
Rastreo del lado del servidor
Para eventos cuya fuente de verdad es su backend — compras, suscripciones, actividad fuera del sitio — envíelos desde el servidor con el punto de terminaison POST /api/v1/events. Esta es una API de ingesta separada y autenticada.
Advertencia
La ingesta del lado del servidor requiere una clave API y un plan pago (Pro, Scale o Enterprise). Las cuentas gratuitas reciben 403 API_PAID_PLAN_REQUIRED. Cree una clave en Settings → Security → API keys — vea Getting an API Key. El script de rastreo del navegador anterior permanece libre en todos los planes.
Auténtiquese con una clave API zv_* en el encabezado Authorization: Bearer. Cada evento tiene un type (pageview, event, identify, goal o purchase), un ts en milisegundos y un objeto props específico del tipo. Pase un idempotencyKey para que los reintentos sean seguros.
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 };
}
Gestor de rutas API
// 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 });
}
Información
Los eventos pageview necesitan un visitorId y sessionId RFC-4122 reales (las columnas están tipificadas como UUID), por lo que generalmente se dejan mejor al rastreador del navegador. El punto de terminaison del servidor es ideal para los tipos event, identify, goal y purchase. Vea Server-Side Tracking para el esquema de eventos completo y las razones del rechazo.
Compra desde un 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 });
}
Variables de entorno
Configuración
# .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
Advertencia
Solo el código de rastreo (usado en el atributo data-tracking-code) es seguro exponer con el prefijo NEXT_PUBLIC_. Su clave API zv_* debe permanecer en el lado del servidor — nunca la prefije con 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' } }],
}),
});
Análisis por grupos de rutas
// 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 de 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 {};
Patrones comunes
Con 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} />;
}
Solución de problemas
El script no carga
Verifique:
- Que el script esté en el layout/documento
- Que no haya interferencia de bloqueadores de anuncios
- Que el código de rastreo sea correcto
No se registran vistas de página
Asegúrese de:
- Que el script se cargue después del body
- Que no esté en modo de desarrollo (salvo que sea intencional)
- Que los cambios de ruta se detecten
Problemas de hidratación
Use la estrategia afterInteractive:
<Script
src="https://api.zenovay.com/z.js"
data-tracking-code="YOUR_TRACKING_CODE"
strategy="afterInteractive"
/>