Ir al contenido principal
Zenovay
Gratis15 minutesIntermedio

Integración con Next.js

Integra Zenovay con Next.js — compatibilidad con App Router, Pages Router y seguimiento del lado del servidor. Aprende sobre nextjs en esta guía de integraciones API.

nextjsreactintegrationssrapp-router
Última actualización:

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"
/>

Próximos pasos

¿Fue útil este artículo?