Integrate Zenovay analytics into your Next.js application with support for both App Router and Pages Router using the tracking script tag.
Installation
No npm package is needed. Add the Zenovay tracking script to your app using Next.js Script component or a standard <script> tag.
App Router Setup (Next.js 13+)
Add to Root Layout
// 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>
);
}
With Environment Variable
// 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 Component for Events
Create a client component for tracking custom events:
// 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;
}
Add to 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>
);
}
Pages Router Setup
Add to _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"
/>
</>
);
}
Or use Script in _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>
);
}
Event Tracking
Client Component
'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>
);
}
Server Action Tracking
For server-side event tracking, use the tracking endpoint directly:
// app/actions.ts
'use server';
import { headers } from 'next/headers';
export async function submitForm(formData: FormData) {
// Process form
const email = formData.get('email');
const headersList = headers();
// Track on server using the tracking endpoint
await fetch('https://api.zenovay.com/e/YOUR_TRACKING_CODE', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Forwarded-For': headersList.get('x-forwarded-for') || '',
},
body: JSON.stringify({
type: 'form_submitted',
url: headersList.get('referer') || '',
props: {
form: 'contact',
has_email: !!email,
},
}),
});
return { success: true };
}
User Identification
After Authentication
'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;
}
Revenue 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>
);
}
Environment Variables
Setup
# .env.local
NEXT_PUBLIC_ZENOVAY_TRACKING_CODE=your-tracking-code
ZENOVAY_API_KEY=your-api-key # For server-side
Usage
// 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/e/YOUR_TRACKING_CODE', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ type: 'page_view', url: '/page' })
});
Server-Side Analytics
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/e/YOUR_TRACKING_CODE', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Forwarded-For': request.headers.get('x-forwarded-for') || '',
},
body: JSON.stringify({
type: body.type || 'page_view',
url: body.url || '/',
props: body.props,
})
});
return NextResponse.json({ success: true });
}
Track in Server Component
For server components, use the tracking endpoint:
// app/products/[id]/page.tsx
import { headers } from 'next/headers';
export default async function ProductPage({ params }) {
const headersList = headers();
// Track server-side pageview using the tracking endpoint
await fetch('https://api.zenovay.com/e/YOUR_TRACKING_CODE', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Forwarded-For': headersList.get('x-forwarded-for') || '',
'User-Agent': headersList.get('user-agent') || '',
},
body: JSON.stringify({
type: 'page_view',
url: `/products/${params.id}`,
})
});
const product = await getProduct(params.id);
return <ProductDetails product={product} />;
}
Middleware Tracking
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
// Track in middleware for all requests
fetch('https://api.zenovay.com/e/YOUR_TRACKING_CODE', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Forwarded-For': request.headers.get('x-forwarded-for') || '',
'User-Agent': request.headers.get('user-agent') || '',
},
body: JSON.stringify({
type: 'page_view',
url: request.nextUrl.pathname,
referrer: request.headers.get('referer') || '',
})
}).catch(() => {}); // Fire and forget
return NextResponse.next();
}
export const config = {
matcher: '/((?!api|_next/static|_next/image|favicon.ico).*)',
};
Route Groups Analytics
// 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 Types
// 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 {};
Common Patterns
With Next-Auth
// app/providers.tsx
'use client';
import { SessionProvider } from 'next-auth/react';
export function Providers({ children }) {
return (
<SessionProvider>
{children}
</SessionProvider>
);
}
ISR/SSG Pages
// 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} />;
}
Troubleshooting
Script Not Loading
Check:
- Script is in layout/document
- No ad blocker interference
- Tracking code is correct
No Page Views
Ensure:
- Script loads after body
- Not in development mode (unless intended)
- Route changes detected
Hydration Issues
Use afterInteractive strategy:
<Script
src="https://api.zenovay.com/z.js"
data-tracking-code="YOUR_TRACKING_CODE"
strategy="afterInteractive"
/>