React + Vite
Also applies to other single-page apps (Vue, Svelte, Solid) – only the entry file differs.
Install
npm install {{API_ORIGIN}}/v1/sdk/3x-engine-sdk-0.1.0.tgzVITE_X3_KEY={{PUBLISHABLE_KEY}}
VITE_X3_HOST={{API_ORIGIN}}import { init } from "@3x-engine/sdk";
import { forms, sections, video } from "@3x-engine/sdk/plugins";
init({
key: import.meta.env.VITE_X3_KEY,
host: import.meta.env.VITE_X3_HOST,
plugins: [video(), sections(), forms()],
});
// …then createRoot(…).render(…) as beforeCall init before rendering. React Router, TanStack Router and other History-API routers produce
spa pageviews automatically; for hash routers add hashRouting: true.
Verify
npm run dev, open the app, navigate between two routes.POST {{API_ORIGIN}}/v1/ereturns 204; the second route is anspapageview of the same session.- Watch the events arrive on the dashboard's install page.
Vite's dev server runs on http://localhost:5173 – enable Allow localhost in the project's
tracking settings for local testing.
Proxy (optional)
Static SPAs need the proxy at the hosting level. On Vercel, a serverless function:
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 function proxy(request: Request): Promise<Response> | Response {
const method = request.method as "GET" | "POST" | "OPTIONS";
return handler[method] ? handler[method](request) : new Response(null, { status: 405 });
}{ "rewrites": [{ "source": "{{PROXY_PATH}}/:path*", "destination": "/api/n3/:path*" }] }VITE_X3_HOST={{PROXY_PATH}}Other hosts: Netlify, Cloudflare Worker, Nginx. For local development a Vite dev-server proxy is enough:
export default defineConfig({
server: { proxy: { "{{PROXY_PATH}}": { target: "{{API_ORIGIN}}", changeOrigin: true, rewrite: (path) => path.replace(/^\{{PROXY_PATH}}/, "") } } },
});Consent
import { consent } from "@3x-engine/sdk";
export function ConsentBanner({ onDone }: { onDone: () => void }) {
return (
<div role="dialog" aria-label="Cookie consent">
<button onClick={() => { consent("granted"); onDone(); }}>Accept</button>
<button onClick={() => { consent("denied"); onDone(); }}>Decline</button>
</div>
);
}Events
import { track } from "@3x-engine/sdk";
export function PlanPicker() {
return (
<select onChange={(event) => track("plan_selected", { plan: event.currentTarget.value })}>
<option value="starter">Starter</option>
<option value="pro">Pro</option>
</select>
);
}Register plan_selected with an enum schema to keep its values clean (Custom events).