Count an exposure only when the variant is actually seen
Resolve a variant in a prefetch or lazy component with logExposure:false, then log the exposure by hand the moment it renders.
Your experiment lives in a tab that most users never open. But your server loader resolves the variant for everyone up front to render the page — so every user gets an exposure, including the ones who never see the tab. Your "exposed" population is now mostly people who weren't exposed to anything, and the effect you're measuring is diluted toward zero.
The fix is manual exposure: resolve the variant without counting it, then count it at the exact moment it renders.
The shape: decouple resolving from logging. Pass logExposure: false when you read the variant, and call logExposure later, once, when the user actually sees it.
1. Resolve without logging
import { configure, Client } from "@shipeasy/sdk/server";
configure({ apiKey: process.env.SHIPEASY_SERVER_KEY! });
const flags = new Client({ user_id: "u_4f2a" });
const result = flags.getExperiment(
"checkout_button",
{ color: "blue" }, // default params
{ logExposure: false }, // resolve only — no exposure yet
);If a whole engine should never auto-log, set it once at construction instead:
import { Engine } from "@shipeasy/sdk/server";
const engine = new Engine({
apiKey: process.env.SHIPEASY_SERVER_KEY!,
disableAutoExposure: true,
});2. Log it when the variant renders
Fire the exposure from the code path that mounts the variant — not the loader:
// in the component / handler that actually shows the variant —
// the client is already bound to the user, so pass just the experiment name
flags.logExposure("checkout_button");If you're holding the Engine directly rather than a bound Client, pass the user too: engine.logExposure({ user_id: "u_4f2a" }, "checkout_button"). In the browser the visitor is already identified, so the bound call is the same: flags.logExposure("checkout_button").
A duplicate exposure for the same user/experiment is deduped by the pipeline, but the clean
contract is: suppress the auto-exposure, then call logExposure exactly once — when
the user sees the variant, not when you computed it.
Rollout & measurement plan
- "Exposed" should mean "saw it." With manual exposure your denominator is the people who actually reached the surface, so the measured lift is the real one.
- Audit the render path. The bug to avoid is logging in a code path that runs even when the component doesn't mount — put the
logExposurecall as close to the render as you can. - Prefetch + lazy UI are the classic cases. Server loaders, tabs, modals, and routes that may never open are exactly where auto-exposure over-counts.
Re-balance a running experiment without corrupting results
Ramp an experiment's allocation 10% → 50% mid-flight while sticky bucketing keeps already-assigned users in the group they started in.
Target on email or PII without it landing in analytics
Use privateAttributes to target on email or tier while stripping those fields from every event the SDK emits.