Docs

Next.js Pages Router

Install

npm install {{API_ORIGIN}}/v1/sdk/3x-engine-sdk-0.1.0.tgz
.env.local
NEXT_PUBLIC_X3_KEY={{PUBLISHABLE_KEY}}
pages/_app.tsx
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:

lib/x3.ts
// 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

  1. npm run dev, open a page, scroll, navigate to another page via a <Link>.
  2. The network tab shows POST {{API_ORIGIN}}/v1/e → 204; the second page arrives as an spa pageview of the same session.
  3. The install page in the dashboard shows the pageviews live.

Proxy (optional)

.env.local
X3_API_ORIGIN={{API_ORIGIN}}
{{PROXY_KEY_ENV}}=sk_3x_YOUR_PROXY_KEY
pages/api{{PROXY_PATH}}/[...path].ts
import { 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 });
}
next.config.js
module.exports = {
  async rewrites() {
    return [{ source: "{{PROXY_PATH}}/:path*", destination: "/api{{PROXY_PATH}}/:path*" }];
  },
};
pages/_app.tsx
<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.

With your own banner, report the decision through the x3() helper from lib/x3.ts:

components/ConsentBanner.tsx
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

components/DownloadLink.tsx
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>
  );
}