Implement Snowplow tracking in the Next.js app
Snowplow Signals computes user attributes from your Snowplow behavioral event stream. Follow these steps to create events to work with.
Signals doesn't need custom events: standard page views, page pings, and link clicks are enough to build meaningful real-time context.
Since you're building a Next.js app, you'll use the Snowplow Browser tracker, which is designed for npm-based frameworks.
Initialize the tracker
Create a module that initializes the Snowplow tracker once, and exports a helper to read the session ID. This module runs client-side only.
// lib/snowplow.ts
import {
newTracker,
trackPageView,
enableActivityTracking,
type BrowserTracker,
} from "@snowplow/browser-tracker";
import {
LinkClickTrackingPlugin,
enableLinkClickTracking,
} from "@snowplow/browser-plugin-link-click-tracking";
let tracker: BrowserTracker | null = null;
export function initSnowplow() {
if (tracker || typeof window === "undefined") return;
tracker = newTracker("sp", process.env.NEXT_PUBLIC_SNOWPLOW_COLLECTOR_URL!, {
appId: "signals-agent",
plugins: [LinkClickTrackingPlugin()],
}) ?? null;
enableActivityTracking({
minimumVisitLength: 30,
heartbeatDelay: 10,
});
enableLinkClickTracking({ pseudoClicks: true });
}
export function trackPage() {
trackPageView();
}
export function getDomainSessionId(): string {
if (!tracker) return "";
try {
// getDomainUserInfo() returns the _sp_id cookie as an array.
// Index [6] is the domain_sessionid.
const info = tracker.getDomainUserInfo();
return info?.[6] ?? "";
} catch {
return "";
}
}
The session ID is stored in the _sp_id cookie, which the getDomainUserInfo() method reads. You'll use this value later to fetch the current user's Signals attributes.
Track page views on route changes
In a Next.js App Router app, client-side navigation doesn't trigger full page reloads.
Create a client component to track page views when the route changes:
// components/snowplow-provider.tsx
"use client";
import { useEffect } from "react";
import { usePathname } from "next/navigation";
import { initSnowplow, trackPage } from "@/lib/snowplow";
export function SnowplowProvider({ children }: { children: React.ReactNode }) {
const pathname = usePathname();
// Initialize tracker on mount
useEffect(() => {
initSnowplow();
}, []);
// Track page view on every route change
useEffect(() => {
trackPage();
}, [pathname]);
return <>{children}</>;
}
The scaffolded app/layout.tsx already includes font imports, a lang attribute, and CSS classes. Add the SnowplowProvider import and wrap {children} with it:
// app/layout.tsx — add these two changes to your scaffolded layout:
// 1. Add this import at the top
import { SnowplowProvider } from "@/components/snowplow-provider";
// 2. Wrap {children} inside the <body> tag:
<body className={/* ...keep existing classes... */}>
<SnowplowProvider>{children}</SnowplowProvider>
</body>
With this in place, every route change fires a page view event, page pings track ongoing engagement, and link clicks are captured automatically. That gives Signals a rich behavioral event stream to compute attributes from.