React
SplitHubProvider, <Experiment>/<Variant>, useVariant, useValue, useTrack, pageview goals.
@splithub/react renders experiments in any React 18+ app (Vite, CRA, Remix, Astro islands). Next.js App Router has its own package, see Next.js.
npm i @splithub/react @splithub/core
Provider
import { SplitHubProvider } from '@splithub/react';
import config from '../splithub.config';
createRoot(document.getElementById('root')!).render(
<SplitHubProvider config={config}>
<App />
</SplitHubProvider>
);
The provider creates the client synchronously, reads or sets the sh_vid cookie, applies cached live overrides (cloud mode) and starts the event queue. Props: config, visitorId? (SSR: pass the id from the cookie so server and client agree), overrides?, client?, pageviews?, context?, remote?, autoGoals?, devtools?.
Component experiments
import { Experiment, Variant } from '@splithub/react';
<Experiment id="hero-cta">
<Variant name="control"><Hero /></Variant>
<Variant name="bold"><HeroBold /></Variant>
</Experiment>
One <Variant> renders by assignment; the exposure is recorded on mount. Unknown or not-running experiments render the control.
Value experiments
const title = useValue<string>('hero-title');
const price = useValue<number>('price-anchor');
const layout = useValue<{ columns: number; dense: boolean }>('grid');
useValue returns the control's value when the experiment is not running, so the page never breaks when a test is paused.
Hooks
| Hook | Returns |
|---|---|
useVariant(id) |
the variant key ('control', 'bold'…); records an exposure the first time |
useValue(id) |
the payload of a value experiment |
useTrack() |
(goal, props?) => void |
usePageview(path) |
fires pageview goals on client-side navigation |
useSplitHub() |
the underlying @splithub/core client |
useVariant is for the same experiment in several places: the visitor gets one variant everywhere.
Goals
const track = useTrack();
return (
<>
<button onClick={() => track('signup')}>Sign up</button>
<button onClick={() => track('purchase', { revenue: 49 })}>Buy</button>
</>
);
Pageview goals fire on mount for the current path; in SPAs call usePageview(location.pathname) once in the layout. Click goals are bound automatically from the config's selectors.
QA
Append ?sh_force=hero-cta:bold to any URL to see a variant, running or draft; no exposure is recorded. Keep the parameter in the URL while previewing; it is not saved across navigation. Several at once: ?sh_force=hero-cta:bold,hero-title:outcome.
To run drafts while you develop without forcing anything, set runDraftsInDev: true in the config (see Drafts in development). Add ?sh_debug=1, or run npx splithub dev, to log every assignment with its reason and get a panel that switches variants. In Vite apps, splithub() from @splithub/core/vite prints what each experiment does in your dev terminal (see Drafts in development).
SSR
Any framework that renders on the server: read the sh_vid cookie, pass it as visitorId to the provider on both sides, set the cookie on the response when missing. @splithub/core exports newVisitorId() and assign() for server-side decisions.
Rules
- Never nest an
<Experiment>inside a<Variant>of the same experiment. - Hooks follow the rules of hooks: no conditional calls.
- Keep the config import at the top of the app; the provider must be above every component that uses a hook.