Integrieren Sie Zenovay Analytics in Ihre Next.js-Anwendung mit Unterstützung für sowohl App Router als auch Pages Router mithilfe des Tracking-Script-Tags.
Installation
Es wird kein npm-Paket benötigt. Fügen Sie das Zenovay-Tracking-Script Ihrer App mithilfe der Next.js Script-Komponente oder eines Standard-<script>-Tags hinzu.
Sie finden Ihren Tracking-Code in der Zenovay-App: Öffnen Sie Domains, klicken Sie auf Ihre Website und öffnen Sie dann deren Seite Allgemeine Einstellungen. Die Karte Tracking-Script zeigt das Snippet und eine Schaltfläche Installation verifizieren.
App Router-Einrichtung (Next.js 13+)
Zum Root-Layout hinzufügen
// 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>
);
}
Mit Umgebungsvariable
// 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>
);
}
Client-Komponente für Events
Erstellen Sie eine Client-Komponente zum Tracking von Seitenaufrufen bei Client-seitiger (SPA) Navigation:
// 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;
}
Zum Layout hinzufügen:
// 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>
);
}
Pages Router-Einrichtung
Zu _app.tsx hinzufügen
// 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"
/>
</>
);
}
Oder Script in _document.tsx verwenden
// 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>
);
}
Event-Tracking
Client-Komponente
'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>
);
}
Benutzeridentifikation
Nach der Authentifizierung
'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;
}
Umsatz-Tracking
E-Commerce-Checkout
'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>
);
}
Information
Bei Käufen, die von Ihrem Backend erfasst werden (z. B. ein Stripe-Webhook), melden Sie den Umsatz stattdessen vom Server aus – siehe Server-Side Tracking weiter unten. Dies gewährleistet eine genaue Umsatzzuordnung auch dann, wenn der Browser nie eine Bestätigungsseite erreicht.
Serverseitiges Tracking
Für Events, bei denen Ihr Backend die Quelle der Wahrheit ist – Käufe, Anmeldungen, Aktivitäten außerhalb der Website – senden Sie diese vom Server mit dem Endpunkt POST /api/v1/events aus. Dies ist eine separate, authentifizierte Erfassungs-API.
Warnung
Server-seitige Erfassung erfordert einen API-Schlüssel und einen kostenpflichtigen Plan (Pro, Scale oder Enterprise). Kostenlose Konten erhalten 403 API_PAID_PLAN_REQUIRED. Erstellen Sie einen Schlüssel unter Settings → Security → API keys – siehe Getting an API Key. Das Browser-Tracking-Script oben bleibt kostenlos auf jedem Plan.
Authentifizieren Sie sich mit einem zv_* API-Schlüssel im Authorization: Bearer-Header. Jedes Event hat einen type (pageview, event, identify, goal oder purchase), einen Millisekunden-ts und ein typ-spezifisches props-Objekt. Übergeben Sie einen idempotencyKey, um Wiederholungen sicher zu machen.
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 };
}
API-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 });
}
Information
pageview-Events benötigen eine echte RFC-4122 visitorId und sessionId (die Spalten sind UUID-typisiert), daher sind sie normalerweise am besten dem Browser-Tracker überlassen. Der Server-Endpunkt ist ideal für event, identify, goal und purchase-Typen. Siehe Server-Side Tracking für das vollständige Event-Schema und Ablehnungsgründe.
Kauf von einem 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 });
}
Umgebungsvariablen
Einrichtung
# .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
Warnung
Nur der Tracking-Code (verwendet im data-tracking-code-Attribut) ist sicher, um mit dem Präfix NEXT_PUBLIC_ offenzulegen. Ihr zv_* API-Schlüssel muss auf der Serverseite bleiben – präfix ihn niemals mit NEXT_PUBLIC_.
Verwendung
// 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' } }],
}),
});
Route Groups-Analytik
// 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"
/>
</>
);
}
TypeScript-Typen
// 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 {};
Häufige Muster
Mit Next-Auth
// app/providers.tsx
'use client';
import { SessionProvider } from 'next-auth/react';
export function Providers({ children }) {
return (
<SessionProvider>
{children}
</SessionProvider>
);
}
ISR/SSG-Seiten
// 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} />;
}
Fehlerbehebung
Script wird nicht geladen
Überprüfen Sie:
- Script befindet sich im Layout/Dokument
- Keine Störung durch Werbeblocker
- Tracking-Code ist korrekt
Keine Seitenaufrufe
Stellen Sie sicher:
- Script wird nach dem Body geladen
- Nicht im Entwicklungsmodus (es sei denn, dies ist beabsichtigt)
- Routenänderungen werden erkannt
Hydration-Probleme
Verwenden Sie die afterInteractive-Strategie:
<Script
src="https://api.zenovay.com/z.js"
data-tracking-code="YOUR_TRACKING_CODE"
strategy="afterInteractive"
/>