Experiments with Next.js and on the server
Server Components
// app/layout.tsx
import { headers } from "next/headers";
import { getAssignments, X3Analytics } from "@x3growth/sdk/next";
import { X3Experiments } from "@x3growth/sdk/react";
export default async function Layout({ children }: { children: React.ReactNode }) {
const assignments = await getAssignments({
apiOrigin: process.env.X3_API_ORIGIN!,
publishableKey: KEY,
proxyKey: process.env.X3_PROXY_KEY!,
headers: await headers(),
path: "/",
});
return (
<html>
<body>
<X3Analytics publishableKey={KEY} experiments={assignments} />
<X3Experiments assignments={assignments}>{children}</X3Experiments>
</body>
</html>
);
}getAssignmentsposts the visitor's IP, user agent, host, path and – when present – the consentedx3_vidcookie toPOST /v1/x/{pk}/assignwith the proxy key (scopeingest:proxy, server env only). It caches per visitor for 60 s and answers{}after 150 ms (everyone control, nothing exposed).getVariant(key, …)is the one-key form.<X3Analytics experiments>inlines the arms (window.__x3xp) so the browser starts with them – no/v1/xrequest on that page load, exposures consistent with the server render.- Pages that render an arm must not be cached across visitors: render them dynamically or mark the
response
private.
First-party proxy
createX3ProxyHandler (route app/n3/[...path]/route.ts) forwards GET /v1/x/{pk} with its query.
With consent (c=1) the HttpOnly x3_vid cookie travels server-to-server as x-3x-vid, so
visitor-sticky experiments work without exposing the id to scripts.
Server helper
import { createX3Server } from "@x3growth/sdk/server";
const x3 = createX3Server({ apiUrl, apiKey: process.env.X3_SECRET_KEY!, publishableKey: KEY, proxyKey: process.env.X3_PROXY_KEY });
const arms = await x3.experiments.assign({ ip, ua, host: "www.example.com", path: "/pricing" });
await x3.leads.addNote({ idempotencyKey: archiveId }, "Ergänzung des Besuchers");- Outside Next.js and without a secret key,
fetchAssignments({ apiOrigin, publishableKey, proxyKey, request })from@x3growth/sdk/servermakes the same call (any fetch-based server). experiments.assignis the source of truth for prices and offers: never let the browser decide what a customer pays. Signed QA tokens (forceToken) are honoured; unsigned overrides are not.leads.addNoteattaches the visitor's follow-up free text to an existing lead – by lead id or by the Idempotency-Key used forleads.create– and retries for up to 60 s while the lead is still being created. It never creates a lead or a conversion. The text is personal data, readable only withleads:read(POST /v1/projects/{project}/leads/notes, scopeleads:write).