Next.js
npm install https://api.3x.example/v1/sdk/3x-engine-sdk-0.1.0.tgz// app/layout.tsx
import { X3Analytics } from "@3x-engine/sdk/next";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
{children}
<X3Analytics publishableKey={process.env.NEXT_PUBLIC_X3_KEY!} host="/n3" />
</body>
</html>
);
}<X3Analytics> renders next/script with strategy="afterInteractive". It does not use
useSearchParams, so static pages stay static. App Router and Pages Router navigations are captured
through the History API.
Props
| Prop | Meaning |
|---|---|
publishableKey | pk_3x_… |
host | API origin or proxy path. Use /n3 with the proxy route; without a proxy the API origin, e.g. https://api.3x.example |
config | Client config fetched on the server with fetchClientConfig() – the tracker starts without a round trip |
nonce | CSP nonce; on dynamically rendered pages Next.js applies the request nonce automatically |
// app/layout.tsx – config fetched on the server (cached by Next for 60 s)
import { X3Analytics, fetchClientConfig } from "@3x-engine/sdk/next";
export default async function RootLayout({ children }: { children: React.ReactNode }) {
const config = await fetchClientConfig({ publishableKey: process.env.NEXT_PUBLIC_X3_KEY!, apiOrigin: process.env.X3_API_ORIGIN! });
return (
<html lang="en">
<body>
{children}
<X3Analytics publishableKey={process.env.NEXT_PUBLIC_X3_KEY!} host="/n3" config={config ?? undefined} />
</body>
</html>
);
}Pages Router
// 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="/n3" />
</>
);
}Events from components
<X3Analytics> loads the script-tag build, so components talk to window.x3. A tiny helper queues
calls until the script has loaded:
// lib/x3.ts
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);
}"use client";
import { x3 } from "@/lib/x3";
export function SignupButton() {
return <button onClick={() => x3("track", "signup_click", { plan: "pro" })}>Start trial</button>;
}Methods that return something (getIds(), identify(), conversion()) are available as
window.x3.getIds() etc. once the script has loaded. Prefer the npm package everywhere instead?
Use <X3Provider> from @3x-engine/sdk/react (React) – then
import { track } from "@3x-engine/sdk" talks to the same tracker. Do not combine both on one page.
Server actions and route handlers
Leads, conversions and server-side events need a secret key and therefore run on the server:
Server helper. Pass getIds().sessionId from the browser so 3x can attach them
to the visit.
Middleware / proxy.ts
If your app has middleware.ts (or proxy.ts), exclude the proxy path from its matcher so tracking
requests are not rewritten or localized:
export const config = { matcher: ["/((?!n3|_next|api|.*\\..*).*)"] };Full recipes: Next.js App Router, Next.js Pages Router.