Skip to main content

Builder.io Page Model Experimentation

Overviewโ€‹

When running A/B/C experiments on full page experiences (e.g. the homepage, landing pages), our preferred approach is to create separate Builder page models per variant rather than conditionally hiding or showing individual components within a single page model. Each variant gets its own unreachable route, and the correct route is resolved server-side based on the active Growthbook feature flag.

This approach keeps Builder clean and easy to manage โ€” content authors can edit each variant in isolation without worrying about complex show/hide logic or interfering with other variants.


When to Use This Patternโ€‹

Use this pattern when:

  • The experiment involves significant layout or content differences between variants (e.g. swapping out entire sections of a page).
  • You want content authors to manage each variant independently in Builder without coupling components together.
  • The experiment is on a page that already has a Builder page model (e.g. homepage, landing pages).

For smaller, more contained experiments (e.g. toggling a single component's style or placement), a feature flag passed directly into a component via the loader is sufficient. See the Growthbook docs for that pattern.


How It Worksโ€‹

1. Create variant page models in Builderโ€‹

For each non-control variant, create a new Builder page model using a hidden, unreachable route. We use the convention:

/t/<feature_flag_key>_<variant>

For example, for a feature flag hp_swatch-bundles-homepage_2026_03 with variants B and C:

/t/hp_swatch-bundles-homepage_2026_03_b   โ† Variant B
/t/hp_swatch-bundles-homepage_2026_03_c โ† Variant C

The /t/ prefix signals that these routes are internal experiment routes and are not linked to from anywhere in the site. Customers cannot navigate to them directly.

The control variant (A) always maps to the canonical route (e.g. /), so no additional Builder model is needed for it.

2. Resolve the route server-side in the loaderโ€‹

In the page's loader, use getGrowthbookFeature to read the active variant and map it to the appropriate Builder route. The resolved route is then passed to loadSingleContent so the correct Builder model is fetched and rendered.

// Example: app/routes/_index.tsx

async function loadContent({ request, context }: Route.LoaderArgs) {
const enableSwatchBundleHPGate = getGrowthbookFeature(
context.growthbook,
'hp_swatch-bundles-homepage_2026_03',
'A'
);

/**
* [Lead Gen - A/B/C Test - March 2026] `enableSwatchBundleHPGate`
* Experiment description, variants, and any other notes here.
*/

let experimentalUrl: string = '/';
switch (true) {
case enableSwatchBundleHPGate === 'B':
experimentalUrl = '/t/hp_swatch-bundles-homepage_2026_03_b';
break;
case enableSwatchBundleHPGate === 'C':
experimentalUrl = '/t/hp_swatch-bundles-homepage_2026_03_c';
break;
default:
experimentalUrl = '/';
}

const content = await loadSingleContent<BuilderContentType>({
request,
context,
model: 'page',
urlPath: experimentalUrl,
});

return { content, urlPath: '/', model: 'page' };
}

Note that urlPath returned from the loader should always be the canonical route (/), not the experimental one. The experimental URL is only used internally to fetch the correct Builder content and should never be exposed to the client or browser URL.

3. Add the analytics hookโ€‹

As with all Growthbook experiments, add useSendGBAnalyticsEvent at the top level of the page component so the variant assignment is tracked on mount:

// app/routes/_index.tsx

const Homepage: FC = () => {
useSendGBAnalyticsEvent('hp_swatch-bundles-homepage_2026_03');
// ...
};

4. Register the feature flag typeโ€‹

Add the new flag to growthbook.d.ts using the appropriate type. For A/B/C tests, use a union of the variant letters:

export type AppFeatures = {
'hp_swatch-bundles-homepage_2026_03': 'A' | 'B' | 'C';
// ...
};

Naming Conventionsโ€‹

ThingConventionExample
Feature flag key<page>_<description>_<YYYY_MM>hp_swatch-bundles-homepage_2026_03
Variant Builder routes/t/<feature_flag_key>_<variant>/t/hp_swatch-bundles-homepage_2026_03_b
Default/control variantAlways A (string) or false (boolean)