Aller au contenu principal
Zenovay
Gratuit15 minutesIntermédiaire

Intégration Next.js

Intégrez Zenovay avec Next.js - App Router, Pages Router et support du suivi côté serveur. En savoir plus sur nextjs dans ce guide des intégrations API.

nextjsreactintegrationssrapp-router
Dernière mise à jour :

Intégrez Zenovay Analytics dans votre application Next.js avec support pour App Router et Pages Router en utilisant la balise de script de suivi.

Installation

Aucun paquet npm n'est nécessaire. Ajoutez le script de suivi Zenovay à votre application en utilisant le composant Script de Next.js ou une balise <script> standard.

Vous pouvez trouver votre code de suivi dans l'application Zenovay : ouvrez Domains, cliquez sur votre site, puis ouvrez sa page de paramètres General. La carte Tracking script affiche le snippet et un bouton Verify Installation.

Configuration App Router (Next.js 13+)

Ajouter au layout racine

// 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>
  );
}

Avec variable d'environnement

// 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>
  );
}

Composant client pour les événements

Créez un composant client pour suivre les pages lors de la navigation côté client (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;
}

Ajouter au 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>
  );
}

Configuration Pages Router

Ajouter à _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 utiliser Script dans _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>
  );
}

Suivi des événements

Composant 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>
  );
}

Identification des utilisateurs

Après authentification

'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;
}

Suivi des revenus

Checkout 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>
  );
}

Info

Pour les achats capturés par votre backend (par exemple un webhook Stripe), signalez plutôt les revenus depuis le serveur — voir Server-Side Tracking ci-dessous. Cela maintient l'attribution des revenus précise même si le navigateur n'atteint jamais une page de confirmation.

Suivi côté serveur

Pour les événements dont votre backend est la source de vérité — achats, inscriptions, activité hors site — envoyez-les depuis le serveur avec le point de terminaison POST /api/v1/events. C'est une API d'ingestion distincte et authentifiée.

Avertissement

L'ingestion côté serveur nécessite une clé API et un plan payant (Pro, Scale ou Enterprise). Les comptes gratuits reçoivent 403 API_PAID_PLAN_REQUIRED. Créez une clé sous Settings → Security → API keys — voir Getting an API Key. Le script de suivi du navigateur ci-dessus reste gratuit sur tous les plans.

Authentifiez-vous avec une clé API zv_* dans l'en-tête Authorization: Bearer. Chaque événement a un type (pageview, event, identify, goal ou purchase), un ts en millisecondes et un objet props spécifique au type. Passez un idempotencyKey pour que les nouvelles tentatives soient sûres.

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 };
}

Gestionnaire de route 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 });
}

Info

Les événements pageview nécessitent une visitorId et une sessionId RFC-4122 réelles (les colonnes sont typées UUID), ils sont donc généralement mieux laissés au suivi du navigateur. Le point de terminaison du serveur est idéal pour les types event, identify, goal et purchase. Voir Server-Side Tracking pour le schéma complet des événements et les raisons du rejet.

Achat depuis 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 d'environnement

Configuration

# .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

Avertissement

Seul le code de suivi (utilisé dans l'attribut data-tracking-code) est sûr d'exposer avec le préfixe NEXT_PUBLIC_. Votre clé API zv_* doit rester côté serveur — ne la préfixez jamais avec NEXT_PUBLIC_.

Utilisation

// 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' } }],
  }),
});

Analytique des groupes de routes

// 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"
      />
    </>
  );
}

Types 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 {};

Modèles courants

Avec Next-Auth

// app/providers.tsx
'use client';

import { SessionProvider } from 'next-auth/react';

export function Providers({ children }) {
  return (
    <SessionProvider>
      {children}
    </SessionProvider>
  );
}

Pages 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} />;
}

Dépannage

Script ne se charge pas

Vérifiez :

  • Le script est dans le layout/document
  • Pas d'interférence du bloqueur de publicités
  • Le code de suivi est correct

Pas de vues de page

Assurez-vous :

  • Le script se charge après le body
  • Pas en mode développement (sauf si intentionnel)
  • Les changements de route sont détectés

Problèmes d'hydratation

Utilisez la stratégie afterInteractive :

<Script
  src="https://api.zenovay.com/z.js"
  data-tracking-code="YOUR_TRACKING_CODE"
  strategy="afterInteractive"
/>

Prochaines étapes

Cet article vous a-t-il aidé ?