@elevateab/sdk
v1.3.1
Published
Elevate AB Testing SDK for Hydrogen and Remix frameworks
Downloads
1,286
Readme
@elevateab/sdk
A/B Testing SDK for Shopify Hydrogen and Next.js stores.
Installation
npm install @elevateab/sdkPeer Dependencies:
react>= 18.0.0 or >= 19.0.0@shopify/hydrogen>= 2023.10.0 (Hydrogen only)next>= 13.0.0 (Next.js only)
Hydrogen Setup
1. Add the Vite Plugin
// vite.config.ts
import {elevate} from '@elevateab/sdk/vite';
export default defineConfig({
plugins: [hydrogen(), oxygen(), elevate(), reactRouter(), tsconfigPaths()],
// ...
});The elevate() plugin configures Vite's SSR bundling so the SDK works correctly with Hydrogen's analytics during development (HMR). This is required for Hydrogen stores.
2. Add the Provider
Hydrogen uses automatic analytics tracking via Shopify's useAnalytics() hook.
// app/root.tsx
import { ElevateProvider, ElevateAnalytics } from "@elevateab/sdk";
import { Analytics, useNonce } from "@shopify/hydrogen";
export default function Root() {
const data = useLoaderData<typeof loader>();
const nonce = useNonce(); // Required for CSP compliance
return (
<Analytics.Provider
cart={data.cart}
shop={data.shop}
consent={data.consent}
>
<ElevateProvider
storeId="mystore.myshopify.com"
storefrontAccessToken={env.PUBLIC_STOREFRONT_API_TOKEN}
preventFlickering={true}
nonce={nonce}
>
<ElevateAnalytics />
<Outlet />
</ElevateProvider>
</Analytics.Provider>
);
}That's it! Analytics events are tracked automatically when users view pages, products, add to cart, etc.
The preventFlickering={true} prop prevents content flash while tests load.
Content Security Policy Configuration
For Hydrogen stores: Add Elevate CDN domains to your CSP in app/entry.server.tsx:
// app/entry.server.tsx
export default async function handleRequest(
request: Request,
responseStatusCode: number,
responseHeaders: Headers,
reactRouterContext: EntryContext,
context: HydrogenRouterContextProvider,
) {
const {nonce, header, NonceProvider} = createContentSecurityPolicy({...});
// Add Elevate domains to CSP
let elevateCSP = header
.replace(
/script-src-elem\s+([^;]+)/,
`script-src-elem $1 https://d19dz5bxptjmv5.cloudfront.net`
)
.replace(
/connect-src\s+([^;]+)/,
`connect-src $1 https://d19dz5bxptjmv5.cloudfront.net https://d339co84ntxcme.cloudfront.net https://configs.elevateab.com`
);
responseHeaders.set('Content-Security-Policy', elevateCSP);
// ... rest of your code
}For other frameworks (Next.js, Gatsby, etc.):
Add the following CSP header:
Content-Security-Policy: script-src 'self' https://d19dz5bxptjmv5.cloudfront.net; script-src-elem 'self' https://d19dz5bxptjmv5.cloudfront.net; connect-src 'self' https://d19dz5bxptjmv5.cloudfront.net https://d339co84ntxcme.cloudfront.net https://configs.elevateab.comNext.js Setup
Next.js requires manual tracking since it doesn't have Shopify's analytics system.
1. Add the Provider
// app/layout.tsx
import { ElevateNextProvider } from "@elevateab/sdk/next";
export default function RootLayout({ children }) {
return (
<html>
<body>
<ElevateNextProvider
storeId="mystore.myshopify.com"
storefrontAccessToken={process.env.NEXT_PUBLIC_STOREFRONT_TOKEN}
preventFlickering={true}
>
{children}
</ElevateNextProvider>
</body>
</html>
);
}The ElevateNextProvider automatically:
- Tracks page views on route changes
- Initializes analytics globally
- Prevents content flicker (when
preventFlickering={true})
2. Track Product Views
// app/product/[handle]/page.tsx
import { ProductViewTracker } from "@elevateab/sdk/next";
export default function ProductPage({ product }) {
return (
<>
<ProductViewTracker
productId={product.id}
productVendor={product.vendor}
productPrice={parseFloat(product.priceRange.minVariantPrice.amount)}
currency={product.priceRange.minVariantPrice.currencyCode}
/>
{/* Product content */}
</>
);
}3. Track Add to Cart
import { trackAddToCart } from "@elevateab/sdk";
async function handleAddToCart() {
// Shopify GIDs are automatically converted to numeric IDs
await trackAddToCart({
productId: product.id, // "gid://shopify/Product/123" works
variantId: variant.id, // "gid://shopify/ProductVariant/456" works
productPrice: 99.99,
productQuantity: 1,
currency: "USD",
cartId: cart.id, // For cart attribute tagging
});
}4. Other Tracking Events
import {
trackRemoveFromCart,
trackCartView,
trackSearchSubmitted,
trackCheckoutStarted,
trackCheckoutCompleted,
} from "@elevateab/sdk";
// Remove from cart
await trackRemoveFromCart({
productId: product.id,
variantId: variant.id,
productPrice: 99.99,
productQuantity: 1,
});
// Cart view
await trackCartView({
cartTotalPrice: 199.99,
cartTotalQuantity: 2,
currency: "USD",
cartItems: [
{ productId: "123", variantId: "456", productPrice: 99.99, productQuantity: 1 },
],
});
// Search
await trackSearchSubmitted({ searchQuery: "blue shirt" });
// Checkout started
await trackCheckoutStarted({
cartTotalPrice: 199.99,
currency: "USD",
cartItems: [...],
});
// Checkout completed (order placed)
await trackCheckoutCompleted({
orderId: "order_123",
cartTotalPrice: 199.99,
currency: "USD",
cartItems: [...],
});Using A/B Tests
useExperiment Hook
import { useExperiment } from "@elevateab/sdk";
function PricingSection() {
const { variant, isLoading } = useExperiment("pricing-test");
if (isLoading) return <LoadingSkeleton />;
if (variant?.isControl) {
return <Price amount={99.99} />;
}
return <Price amount={79.99} />;
}Variant Properties
const { variant } = useExperiment("test-id");
variant?.isControl; // true if control group
variant?.isA; // true if variant A
variant?.isB; // true if variant B
variant?.isC; // true if variant C
variant?.isD; // true if variant D
variant?.id; // variant ID
variant?.name; // variant nameMultiple Variants
function LayoutTest() {
const { variant } = useExperiment("layout-test");
if (variant?.isA) return <LayoutA />;
if (variant?.isB) return <LayoutB />;
if (variant?.isC) return <LayoutC />;
return <DefaultLayout />;
}Preview Mode
Test specific variants without affecting live traffic. Add URL parameters:
https://yourstore.com/?eabUserPreview=true&abtid=<test-id>&eab_tests=<short-id>_<variant-id>Example:
https://yourstore.com/products/shirt?eabUserPreview=true&abtid=abc123&eab_tests=c123_12345Check if in preview mode:
import { isPreviewMode } from "@elevateab/sdk";
if (isPreviewMode()) {
// Show preview indicator
}Cart Attribute Tagging
Orders are attributed to A/B tests via cart attributes. This happens automatically when you provide storefrontAccessToken and cartId.
For Hydrogen, pass storefrontAccessToken and nonce to the provider:
const nonce = useNonce();
<ElevateProvider
storeId="mystore.myshopify.com"
storefrontAccessToken={env.PUBLIC_STOREFRONT_API_TOKEN}
nonce={nonce}
/>;For Next.js, pass cartId to trackAddToCart:
trackAddToCart({
productId: "123",
variantId: "456",
cartId: cart.id,
// ...
});The Storefront Access Token is safe to use client-side - it's a public token designed for browser use.
Utility Functions
Shopify ID Helpers
import { extractShopifyId, isShopifyGid } from "@elevateab/sdk";
extractShopifyId("gid://shopify/Product/123456"); // "123456"
isShopifyGid("gid://shopify/Product/123"); // trueNote: Tracking functions automatically extract IDs, so you rarely need these directly.
API Reference
ElevateProvider Props
interface ElevateProviderProps {
storeId: string; // Required: your-store.myshopify.com
storefrontAccessToken?: string; // For cart attribute tagging
preventFlickering?: boolean; // Prevent content flash during test load (recommended)
nonce?: string; // Only needed if you have strict CSP headers
children: React.ReactNode;
}Analytics Events Summary
| Event | Hydrogen | Next.js |
| ------------------ | --------- | -------------------------- |
| Page view | Automatic | Automatic |
| Product view | Automatic | ProductViewTracker |
| Add to cart | Automatic | trackAddToCart() |
| Remove from cart | Automatic | trackRemoveFromCart() |
| Cart view | Automatic | trackCartView() |
| Search | Automatic | trackSearchSubmitted() |
| Checkout started | Automatic | trackCheckoutStarted() |
| Checkout completed | Automatic | trackCheckoutCompleted() |
License
MIT
