Next.js
Integrate Vendo web tracking with Next.js — App Router and Pages Router setup with client-side navigation tracking.
Last reviewed July 25, 2026
Add Vendo tracking to your Next.js app. Both App Router and Pages Router are supported.
App Router
Add the CDN snippet to your root layout using Next.js’s <Script> component:
// app/layout.tsx
import Script from 'next/script';
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<head>
<Script id="vendo-tracking" strategy="afterInteractive">
{`
(function(w,d,s,e,l,k){w['VendoObject']=e;w[e]=w[e]||function(){
(w[e].q=w[e].q||[]).push(arguments)};w[e].l=1*new Date();
l=d.createElement(s);k=d.getElementsByTagName(s)[0];
l.async=1;l.src='https://cdn.vendodata.com/sdk/v1/vendo.js';k.parentNode.insertBefore(l,k);
})(window,document,'script','vendo');
vendo('init', 'YOUR_WRITE_KEY', {
host: 'https://YOUR_TRACKING_ENDPOINT'
});
`}
</Script>
</head>
<body>{children}</body>
</html>
);
}Track Client-Side Navigations
Page views are explicit — since SDK 0.2.0 init never sends one automatically. Add a route-change listener component; it sends one canonical Page Viewed event for the initial route and each client-side pathname or query-string change:
// components/track-page-views.tsx
'use client';
import { usePathname, useSearchParams } from 'next/navigation';
import { useEffect } from 'react';
declare global {
interface Window {
vendo?:
| ((...args: unknown[]) => void)
| {
page: (name?: string, properties?: Record<string, unknown>) => void;
identify: (userId: string, traits?: Record<string, unknown>) => void;
reset: () => void;
};
}
}
function trackPageView(pathname: string, search: string) {
const page = search ? `${pathname}?${search}` : pathname;
const properties = { path: pathname, search, page };
if (typeof window.vendo === 'function') {
window.vendo('page', 'Page Viewed', properties);
} else {
window.vendo?.page('Page Viewed', properties);
}
}
export function TrackPageViews() {
const pathname = usePathname();
const searchParams = useSearchParams();
const search = searchParams.toString();
useEffect(() => {
if (!window.vendo) return;
trackPageView(pathname, search);
}, [pathname, search]);
return null;
}Add <TrackPageViews /> to your root layout inside a <Suspense> boundary (required by useSearchParams):
// app/layout.tsx (body section)
import { Suspense } from 'react';
import { TrackPageViews } from '@/components/track-page-views';
<body>
<Suspense fallback={null}>
<TrackPageViews />
</Suspense>
{children}
</body>;Pages Router
Add the snippet to _app.tsx and track route changes with the Next.js router:
// pages/_app.tsx
import type { AppProps } from 'next/app';
import Script from 'next/script';
import { useRouter } from 'next/router';
import { useEffect } from 'react';
export default function App({ Component, pageProps }: AppProps) {
const router = useRouter();
useEffect(() => {
const trackCurrentPage = (url: string) => {
const nextUrl = new URL(url, window.location.origin);
trackPageView(nextUrl.pathname, nextUrl.search.slice(1));
};
trackCurrentPage(window.location.href);
router.events.on('routeChangeComplete', trackCurrentPage);
return () => router.events.off('routeChangeComplete', trackCurrentPage);
}, [router.events]);
return (
<>
<Script id="vendo-tracking" strategy="afterInteractive">
{`
(function(w,d,s,e,l,k){w['VendoObject']=e;w[e]=w[e]||function(){
(w[e].q=w[e].q||[]).push(arguments)};w[e].l=1*new Date();
l=d.createElement(s);k=d.getElementsByTagName(s)[0];
l.async=1;l.src='https://cdn.vendodata.com/sdk/v1/vendo.js';k.parentNode.insertBefore(l,k);
})(window,document,'script','vendo');
vendo('init', 'YOUR_WRITE_KEY', {
host: 'https://YOUR_TRACKING_ENDPOINT'
});
`}
</Script>
<Component {...pageProps} />
</>
);
}Identify Users
Call identify once the user is authenticated:
const authUserId = user.id; // Use the same stable ID sent to Mixpanel.
if (typeof window.vendo === 'function') {
window.vendo('identify', authUserId, { plan: user.plan });
} else {
window.vendo?.identify(authUserId, { plan: user.plan });
}Call reset on logout to clear the identity:
if (typeof window.vendo === 'function') {
window.vendo('reset');
} else {
window.vendo?.reset();
}If Mixpanel is also installed, call mixpanel.identify(authUserId) and mixpanel.reset() at the same lifecycle points. This lets Vendo events and Mixpanel browser activity resolve to the same user profile. Use an opaque stable application ID rather than an email address as the primary identity key.
Related
- JavaScript SDK — Full method reference and configuration options
- Quickstart — Get started in 5 minutes