Next.js Pages Router
Install
npm install {{API_ORIGIN}}/v1/sdk/3x-engine-sdk-0.1.0.tgzNEXT_PUBLIC_X3_KEY={{PUBLISHABLE_KEY}}import type { AppProps } from "next/app";
import { X3Analytics } from "@3x-engine/sdk/next";
export default function App({ Component, pageProps }: AppProps) {
return (
<>
<Component {...pageProps} />
<X3Analytics publishableKey={process.env.NEXT_PUBLIC_X3_KEY!} host="{{API_ORIGIN}}" />
</>
);
}Router navigations are captured through the History API – no Router.events wiring is needed.
Add a small helper so components can call the tracker that <X3Analytics> loads – it queues
calls until the script is ready and is used by the consent and event examples below:
// Calls the tracker that <X3Analytics> loads (window.x3). Commands issued before the
// script has loaded are queued and replayed in order.
type X3Command = (...args: unknown[]) => void;
export function x3(...args: unknown[]): void {
if (typeof window === "undefined") return;
const w = window as unknown as { x3?: X3Command; x3q?: unknown[][] };
w.x3 ??= (...queued: unknown[]) => void (w.x3q ??= []).push(queued);
w.x3(...args);
}Verify
npm run dev, open a page, scroll, navigate to another page via a<Link>.- The network tab shows
POST {{API_ORIGIN}}/v1/e→ 204; the second page arrives as anspapageview of the same session. - The install page in the dashboard shows the pageviews live.
Proxy (optional)
X3_API_ORIGIN={{API_ORIGIN}}
{{PROXY_KEY_ENV}}=sk_3x_YOUR_PROXY_KEYimport { createX3ProxyHandler } from "@3x-engine/sdk/next/proxy";
const handler = createX3ProxyHandler({ apiOrigin: process.env.X3_API_ORIGIN!, proxyKey: process.env.{{PROXY_KEY_ENV}} });
export const config = { runtime: "edge" };
export default async function proxy(request: Request): Promise<Response> {
const method = request.method as "GET" | "POST" | "OPTIONS";
return handler[method] ? handler[method](request) : new Response(null, { status: 405 });
}module.exports = {
async rewrites() {
return [{ source: "{{PROXY_PATH}}/:path*", destination: "/api{{PROXY_PATH}}/:path*" }];
},
};<X3Analytics publishableKey={process.env.NEXT_PUBLIC_X3_KEY!} host="{{PROXY_PATH}}" />The rewrite keeps the public path short ({{PROXY_PATH}}/v1/e) while the handler lives under
pages/api. Exclude the path from middleware.ts if you have one.
Consent
With your own banner, report the decision through the x3() helper from lib/x3.ts:
import { x3 } from "../lib/x3";
export function ConsentButtons() {
return (
<>
<button onClick={() => x3("consent", "granted")}>Accept</button>
<button onClick={() => x3("consent", "denied")}>Decline</button>
</>
);
}Or select your CMP in the project's consent settings (Consent).
Events
import { x3 } from "../lib/x3";
export function DownloadLink() {
return (
<a href="/whitepaper.pdf" onClick={() => x3("track", "whitepaper_download", { topic: "pricing" }, { flush: true })}>
Download the whitepaper
</a>
);
}