# Shopify Hydrogen

This guide covers integrating Elevate A/B Testing into your Shopify Hydrogen storefront using the `@elevateab/sdk` package.

> **Using a standard Shopify theme?** This guide is for headless Hydrogen storefronts only. For standard Shopify theme integration, see [Setup & Installation](https://docs.elevateab.com/elevate-helpcenter/getting-started/setup-and-installation).

### Prerequisites

Before starting, make sure you've completed the steps in [Headless Getting Started](https://docs.elevateab.com/elevate-helpcenter/headless-integration/getting-started-with-headless):

* Installed the `@elevateab/sdk` package
* Located your Storefront Access Token
* Familiarized yourself with the `useExperiment` hook

### Provider Setup

Hydrogen has a built-in analytics system via Shopify's `Analytics.Provider`. To get Elevate tracking working, all you need to do is add `ElevateHydrogenAnalytics` inside `Analytics.Provider` — that single line handles all event tracking automatically.

Here's the full setup in `app/root.tsx`:

```tsx
// app/root.tsx
import { Analytics, useNonce } from "@shopify/hydrogen";
import { ElevateProvider, ElevateHydrogenAnalytics } from "@elevateab/sdk";
import { Outlet, useLoaderData } from "@remix-run/react";

export default function App() {
  const { cart, shop, consent } = useLoaderData<typeof loader>();
  const nonce = useNonce();

  return (
    <html>
      <body>
        <Analytics.Provider cart={cart} shop={shop} consent={consent}>
          <ElevateProvider
            storeId="your-store.myshopify.com"
            storefrontAccessToken={env.PUBLIC_STOREFRONT_API_TOKEN}
            preventFlickering={true}
            nonce={nonce}
          >
            <ElevateHydrogenAnalytics />
            <Outlet />
          </ElevateProvider>
        </Analytics.Provider>
      </body>
    </html>
  );
}
```

The key pieces:

* **`Analytics.Provider`** — Shopify's built-in analytics system. You likely already have this if your Hydrogen project was scaffolded with the Shopify CLI.
* **`ElevateProvider`** — Loads your A/B test configuration and assigns visitors to variants.
* **`ElevateHydrogenAnalytics`** — This is the important part. This single component subscribes to all of Hydrogen's analytics events and forwards them to Elevate with A/B test data attached. It just needs to be inside `Analytics.Provider`, and everything is automatic from there.

The order matters: `Analytics.Provider` must wrap `ElevateProvider`, and `ElevateHydrogenAnalytics` must be inside both.

### Loader Setup

`Analytics.Provider` requires three props — `cart`, `shop`, and `consent` — which come from your root loader. Here's an example:

```tsx
// app/root.tsx
export async function loader({ context }: LoaderFunctionArgs) {
  const cart = await context.cart.get();

  return {
    cart,
    shop: {
      id: context.env.SHOP_ID,
      name: "Your Store",
      currency: "USD",
    },
    consent: {
      checkoutDomain: context.env.PUBLIC_STORE_DOMAIN,
      storefrontAccessToken: context.env.PUBLIC_STOREFRONT_TOKEN,
    },
  };
}
```

If your Hydrogen project was scaffolded with Shopify's CLI, you likely already have most of this in place. Just make sure all three values (`cart`, `shop`, `consent`) are returned from the loader and passed to `Analytics.Provider`.

### Running an Experiment

Once the provider is set up, you can create experiments directly on the Elevate dashboard — split URL, custom code, or content editor experiments all work out of the box without any code changes in your storefront.

If you want to conditionally render content in your codebase, you can optionally use the `useExperiment` hook. Pass in the experiment ID from your Elevate dashboard URL (`elevateab.app/<test-id>`):

```tsx
import { useExperiment } from "@elevateab/sdk";

function HeroBanner() {
  const { isControl, isA, isB, isLoading } = useExperiment("abc123");

  if (isLoading) return <HeroBannerSkeleton />;

  if (isControl) return <h1>Welcome to Our Store</h1>;
  if (isA) return <h1>Shop Our Spring Collection</h1>;
  if (isB) return <h1>Free Shipping on Orders Over $50</h1>;

  return <h1>Welcome to Our Store</h1>;
}
```

For more details on creating experiments, finding your test ID, and using the hook, see [Headless Getting Started.](https://docs.elevateab.com/elevate-helpcenter/headless-integration/getting-started-with-headless)

### Automatic Event Tracking

With `ElevateHydrogenAnalytics` in place, the following events are tracked automatically — no additional code needed:

* **Page views** — every route navigation
* **Product views** — product page visits
* **Add to cart** — items added to cart
* **Remove from cart** — items removed from cart
* **Cart views** — cart page visits
* **Checkout started** — customer begins checkout
* **Checkout completed** — order placed

These events are tied to the visitor's A/B test variant so you can see conversion data broken down by variant in your Elevate dashboard.

You do not need to manually call any tracking functions in Hydrogen. If you've previously used manual tracking (e.g., `trackAddToCart`), you can remove those calls — `ElevateHydrogenAnalytics` handles everything.

### Content Security Policy (CSP)

Hydrogen enforces a strict Content Security Policy by default, which means you'll need to allow the Elevate CDN domains. Without this, the SDK scripts may be blocked by the browser.

Add the following domains to your CSP configuration in `app/entry.server.tsx`:

```
script-src: https://ds0wlyksfn0sb.cloudfront.net
script-src-elem: https://ds0wlyksfn0sb.cloudfront.net
connect-src: https://ds0wlyksfn0sb.cloudfront.net https://d339co84ntxcme.cloudfront.net https://configs.elevateab.com
```

How you add these depends on how your Hydrogen project manages CSP. If you're using Hydrogen's built-in CSP helpers, it typically looks something like this:

```tsx
// app/entry.server.tsx
const { nonce, header, NonceProvider } = createContentSecurityPolicy({
  scriptSrc: [
    "'self'",
    "https://ds0wlyksfn0sb.cloudfront.net",
  ],
  scriptSrcElem: [
    "'self'",
    "https://ds0wlyksfn0sb.cloudfront.net",
  ],
  connectSrc: [
    "'self'",
    "https://ds0wlyksfn0sb.cloudfront.net",
    "https://d339co84ntxcme.cloudfront.net",
    "https://configs.elevateab.com",
  ],
});
```

The `nonce` value from `useNonce()` is passed to `ElevateProvider` (shown in the provider setup above) so that inline scripts pass CSP validation.

### Preview Mode

You can preview a specific variant without affecting live traffic or analytics by adding URL parameters:

```
https://your-store.com/products/example?eabUserPreview=true&abtid=<test-id>&eab_tests=<short-id>_<variant-id>
```

You'll find the test ID, short ID, and variant IDs in your Elevate dashboard under the experiment settings.

To check whether preview mode is active in your code:

```tsx
import { isPreviewMode } from "@elevateab/sdk";

if (isPreviewMode()) {
  // Optionally show a preview indicator banner
}
```

Preview visits are excluded from analytics and won't affect your experiment results.

### Troubleshooting

**Tests aren't showing / visitors all see the control** Make sure the experiment is set to "Running" in your Elevate dashboard, and that the `storeId` in your provider matches your Shopify domain exactly (e.g., `your-store.myshopify.com`, not your custom domain).

**Orders aren't attributed to variants** Check that you're passing `storefrontAccessToken` to `ElevateProvider`. Without it, tests still work but order attribution is disabled.

**Console errors about blocked scripts** This is a CSP issue. See the [CSP Configuration](#content-security-policy-csp) section above and make sure all three Elevate domains are allowlisted.

**Content flickers before the test loads** Make sure `preventFlickering={true}` is set on `ElevateProvider`. This hides content briefly until the test configuration loads, preventing a flash of the wrong variant.
