Pular para o conteúdo principal
Zenovay
Gratuito15 minutesIntermediário

Integração com Next.js

Integre o Zenovay com Next.js — App Router, Pages Router e suporte a rastreamento no lado do servidor. Saiba mais sobre nextjs neste guia de integrações de API.

nextjsreactintegrationssrapp-router
Última atualização:

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

Próximos Passos

Este artigo foi útil?