Skip to main content

Client API

:::caution Pre-release This is a pre-1.0 library (v0.1.1) — API may change without notice. :::

Client-side SDK for browser environments. Import from @next-story/journey-recorder/client.

initJourney(config)

Initialize the journey recorder and start tracking user interactions.

import { initJourney } from "@next-story/journey-recorder/client";

const journey = initJourney({
siteId: "my-site",
collectUrl: "/api/journey/collect",
conversionRoutePattern: "/checkout",
});

Parameters

config: JourneyConfig

  • siteId (required): string
    The site's canonical key, used for diagnostics and demo readability. Never sent in the request body; the collector derives the actual site_id from the verified token's origin claim.

  • collectUrl (required): string
    Same-origin Route Handler path, e.g. /api/journey/collect.

  • identityResolver (optional): () => string
    A function that returns a first-party identifier. Called on initialization. Defaults to minting and reading the __ns_aid cookie. The authoritative identity resolution happens server-side.

  • conversionRoutePattern (optional): string
    One URL pattern. A page_view whose normalized route_pattern matches this string automatically emits a conversion event. Example: /checkout (exact match only; no regex, no wildcards).

  • releaseId (optional): string
    Release identifier, e.g. from process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA. Stamped on every event batch for debugging version-specific issues.

  • requireConsent (optional): boolean
    When true, recording fails closed unless globalThis.__nsJourneyConsent = true is set. Default: false. See Consent for details.

Returns

Journey

An object with three methods:

interface Journey {
track(name: "conversion", props?: { name?: string }): void;
reportRoute(pathname: string): void;
stop(): void;
}
  • track("conversion", props?): Explicitly emit a conversion event. The name parameter in props is optional and becomes conversion_name in the event. Useful for checkout completions, sign-ups, or other goal actions.

  • reportRoute(pathname): Report a route change. Call with usePathname() in a useEffect(). Idempotent — duplicate calls are deduplicated client-side. Intentionally does not read useSearchParams() to preserve privacy (query strings are normalized away).

  • stop(): Tear down the recorder. Call during component unmount or HMR. After calling stop(), a subsequent initJourney() call will restart.

Example

"use client";
import { initJourney } from "@next-story/journey-recorder/client";
import { useEffect } from "react";
import { usePathname } from "next/navigation";

export function JourneyRecorder() {
const pathname = usePathname();

useEffect(() => {
const journey = initJourney({
siteId: "my-site",
collectUrl: "/api/journey/collect",
});

journey.reportRoute(pathname);

// Explicit conversion on button click
const handleCheckout = () => {
journey.track("conversion", { name: "checkout_click" });
};

window.addEventListener("checkout-click", handleCheckout);
return () => {
window.removeEventListener("checkout-click", handleCheckout);
journey.stop();
};
}, [pathname]);

return null;
}

useJourneyRoute() (Next.js App Router)

Route source for the App Router. Call from a "use client" component's effect with usePathname():

import { useJourneyRoute } from "@next-story/journey-recorder/client";
import { usePathname } from "next/navigation";

export function RouteReporter() {
const pathname = usePathname();
useJourneyRoute(pathname);
return null;
}

Or inline in your layout:

"use client";
import { usePathname } from "next/navigation";
import { initJourney } from "@next-story/journey-recorder/client";
import { useEffect } from "react";

export default function RootLayout({ children }: { children: React.ReactNode }) {
const pathname = usePathname();

useEffect(() => {
const journey = initJourney({
siteId: "my-site",
collectUrl: "/api/journey/collect",
});
journey.reportRoute(pathname);
return () => journey.stop();
}, [pathname]);

return <html><body>{children}</body></html>;
}

Events per Session

The client enforces a hard cap of 200 events per session (defined in packages/journey-recorder/src/shared/event.ts). Events after the 200th are dropped client-side, and a truncated flag is set on all subsequent batches so the server can log the condition.

When requireConsent is true, no events are recorded until globalThis.__nsJourneyConsent = true is explicitly set by your consent banner:

// When user opts in:
globalThis.__nsJourneyConsent = true;

// When user opts out:
globalThis.__nsJourneyConsent = false;

Listen for consent changes:

import { onConsentChange } from "@next-story/journey-recorder/client";

onConsentChange((consentGranted) => {
console.log("Consent changed:", consentGranted);
});