Setting Up a Growthbook Experiment
Feature Flag Typesโ
When creating a new feature flag or experiment, add the new type to the growthbook.d.ts file with its respective type. See more about feature types here.
General rule of thumb: if the experiment is A/B (two variants; one control, one experiment), use boolean. If testing multiple variants (A/B/C), use a union of the variant letters. In the AppFeatures map, strictly use the values being set instead of a generic string:
export type AppFeatures = {
'cart_[xxx-xxx-xxx]_2026-01': boolean;
'pdp_[xxx-xxx-xxx-]_2026_01': 'A' | 'B' | 'C';
'lp_[xxx-xxx-xxx]_2026_01': 'A' | 'B' | 'C';
// ...
};
Server-side: getGrowthbookFeatureโ
This server-side helper wraps GB's getFeatureValue function (see here), giving us a typed interface to read feature flag values in loaders.
// app/lib/growthbook/helper.server.ts
export const getGrowthbookFeature = <FeatureType extends keyof AppFeatures>(
growthbook: GrowthBook,
featureKey: FeatureType,
defaultValue: AppFeatures[FeatureType]
) => growthbook.getFeatureValue(featureKey, defaultValue);
How it's used in a loader:
// products.$handle.tsx
const enableColorSelectorPlacement = getGrowthbookFeature(
args.context.growthbook,
'pdp_[xxx-xxx-xxx]_2026_01',
'A'
);
return {
// other loader variables
enableColorSelectorPlacement,
};
Client-sideโ
Passing the flag to componentsโ
After the feature flag value is brought in via useLoaderData, pass it down to the relevant component. Depending on the experiment's scope, this may be directly into a provider (for broad access across a page) or into a more isolated component.
// products.$handle.tsx
<PdpProvider
enableColorSelectorPlacement={enableColorSelectorPlacement}
>
{children}
</PdpProvider>
// Example of the variable being used in a smaller reusable component:
<div
dangerouslySetInnerHTML={{ __html: description }}
className={clsx(enableColorSelectorPlacement === 'C' && 'hidden')}
/>
Analyticsโ
Add useSendGBAnalyticsEvent at the top level of the page component so the variant assignment is tracked on mount. When a client navigates away, GB persists that value so they get a consistent experience on return.
// products.$handle.tsx
const ProductDetailPage: FC = () => {
useSendGBAnalyticsEvent('pdp_[xxx-xxx-xxx]_2026_01');
// ...
};
Conditional analyticsโ
Some experiments require the analytics hook to only run in specific contexts (e.g. a subset of pages). Since conditionally calling a hook directly violates the rules of hooks, use the ConditionalSiteAnalytics component instead:
// app/lib/experiments/helper.tsx
export const ConditionalSiteAnalytics = ({ feature }: { feature: keyof AppFeatures }) => {
useSendGBAnalyticsEvent(feature);
return null;
};
// app/components/PageLayout.tsx
export function PageLayout({ props }) {
return (
<main>
{isSwatchLP && (
<ConditionalSiteAnalytics feature={'lp_hide-announcement-site-switcher_2026_01' as keyof AppFeatures} />
)}
</main>
);
}
You can verify events are firing by opening DevTools and logging window.dataLayer โ events should have growthbook prepended with the experiment ID/name.