A/B Testing
Every Perspect site worker ships with an injected A/B runtime at app/lib/perspect/ab.ts. It reads experiment configuration from the platform, deterministically assigns each visitor to a variant, and emits exposure + conversion events back to the platform for rollup.
No separate install, no keys to manage — the SDK reuses the site's existing PERSPECT_API_KEY binding. See the API Reference for the full surface.
Quick Start
import { getPerspectAb } from "~/lib/perspect/ab";
export async function loader({ request, context }: LoaderFunctionArgs) {
const { ab, responseHeaders } = await getPerspectAb(
context.cloudflare.env,
context.cloudflare.ctx,
).forRequest(request);
const { variant, enrolled, config } = await ab.getVariant("homepage_hero");
return data({
heroCopy: config?.headline ?? "Welcome",
variant,
enrolled,
}, { headers: responseHeaders });
}
The responseHeaders returned by forRequest may carry a freshly minted perspect_vid cookie — merge it into your response or the next request won't see the same visitor identity.
Tracking Conversions
// Anywhere downstream with an `ab` client in scope
await ab.track("checkout.completed", { orderId, revenue });
track iterates every running flag: if the event is a goal on that flag and this visitor is enrolled, a conversion event is emitted for their assigned variant. Events that aren't goals on any running flag are silently dropped — safe to call liberally.
How Assignment Works
Assignment is purely deterministic: bucketFor(visitorId, flagKey, version) FNV-1a hashes the tuple into one of 10,000 buckets. The platform's server-side validator computes the exact same hash on the same inputs — site and platform must always agree bit-for-bit, which is why the bucketing module is inlined into the SDK rather than fetched at runtime.
If bucket >= trafficAllocationBp the visitor is outside the experiment (not enrolled, default variant returned). Otherwise the bucket is mapped to a variant by cumulative weightBp ranges, with variants sorted by key so both sides resolve identically.
Config SWR
getPerspectAb fetches GET {PERSPECT_BASE_URL}/_ab/config?site={PERSPECT_SITE_NAME} and caches the blob in PERSPECT_CACHE for one hour. After 60 seconds the next request serves the stale blob and kicks off a background revalidation via ctx.waitUntil. Passing ctx to getPerspectAb is strongly recommended — without it, refresh happens on the hot path.
To force a refresh (e.g. from a webhook handler), call refreshConfig():
const ab = getPerspectAb(env, ctx);
await ab.refreshConfig();
See Also
- API Reference — full type and function reference, regenerated from source on every build.