Flagship + Shopify Hydrogen Integration
📘 Github Repository
Overview
This guide demonstrates how to:
Integrate Flagship feature flags with Shopify Hydrogen (React-based framework)
Initialize the Flagship SDK with edge bucketing for optimal performance
Create visitor objects with contextual data
Use feature flags in React components
Handle server-side rendering with Flagship
Implement client-side hydration of feature flags
Conditionally display content based on feature flags
Prerequisites
A Flagship account with API credentials
Setup
Clone the example repository:
git clone https://github.com/flagship-io/flagship-hydrogen-example
cd flagship-hydrogen-example
Install dependencies:
npm install
# or
yarn install
Configure environment variables:
Create a .env file with your Flagship credentials:
VITE_ENV_ID=your_flagship_environment_id
VITE_API_KEY=your_flagship_api_key
Start the development server:
npm run dev
# or
yarn dev
Configure Vite for Flagship SDK
When using Flagship SDK with Hydrogen, proper Vite configuration is essential to prevent bundling issues. Update your vite.config.ts file with the following settings:
export default defineConfig({
// ...other configuration
optimizeDeps: {
exclude: [
'@flagship.io/react-sdk',
'@flagship.io/react-sdk/edge'
],
},
ssr: {
optimizeDeps: {
include: [],
exclude: [],
},
},
});
This configuration:
Excludes both the main and edge bundles from client-side optimization
Prevents Vite from processing the SDK in ways that might break its functionality
⚠️ Important: In Hydrogen (and other edge/SSR environments), always import from the edge bundle:
// Correct import for Hydrogen import { ... } from '@flagship.io/react-sdk/edge';
Initialize Flagship SDK in Hydrogen
The Flagship SDK is initialized at the application level to ensure it's available throughout your Hydrogen store. This is done in the flagship.ts helper file:
import {
Flagship,
FSSdkStatus,
DecisionMode,
LogLevel,
type NewVisitor,
} from '@flagship.io/react-sdk/edge';
import initialBucketing from './bucketing.json';
// Function to start the Flagship SDK
export async function startFlagshipSDK() {
if (
Flagship.getStatus() &&
Flagship.getStatus() !== FSSdkStatus.SDK_NOT_INITIALIZED
) {
return Flagship; // If it has been initialized, return early
}
return await Flagship.start(
import.meta.env.VITE_ENV_ID,
import.meta.env.VITE_API_KEY,
{
logLevel: LogLevel.DEBUG, // Set the log level
fetchNow: false, // Do not fetch flags immediately
decisionMode: DecisionMode.BUCKETING_EDGE, // set decision mode
initialBucketing, // Set initial bucketing data
},
);
}
The SDK is configured with:
Edge Bucketing Mode: Makes flag decisions at the edge without API calls
Initial Bucketing Data: Pre-loaded campaign data for local decision-making
Debug Logging: Helps troubleshoot during development
Create a Visitor in Hydrogen's Root Loader
In Hydrogen, the ideal place to create the Flagship visitor is in the root loader function. This ensures it's available for both server-side rendering and client-side hydration:
// In root.tsx
export async function loader(args: LoaderFunctionArgs) {
// ...other loader code
// Initialize Flagship visitor data
const fsVisitorData = {
visitorId: 'visitorId', // In a real app, use a unique ID per user
context: {
key: 'value', // Add relevant context data for targeting
},
hasConsented: true, // This should be set based on user consent
};
// Fetch the Flagship visitor data
const visitor = await getFsVisitorData(fsVisitorData);
return {
// ...other data
fsInitialFlags: visitor.getFlags().toJSON(), // Serialize flags for client
fsVisitorData, // Pass visitor data for hydration
};
}
The getFsVisitorData
function handles SDK initialization and flag fetching:
export async function getFsVisitorData(visitorData: NewVisitor) {
// Start the SDK in Decision Api mode and get the Flagship instance
const flagship = await startFlagshipSDK();
// Create a visitor
const visitor = flagship.newVisitor(visitorData);
// Fetch flag values for the visitor
await visitor.fetchFlags();
// Return visitor instance
return visitor;
}
Provide Flagship Context to Your Application
To make Flagship available throughout your application, use the FsProvider
component to wrap your application:
// In FsProvider.tsx
import {
DecisionMode,
FlagshipProvider,
LogLevel,
SerializedFlagMetadata,
VisitorData,
} from '@flagship.io/react-sdk/edge';
export function FsProvider({
children,
initialFlagsData,
visitorData,
}: {
children: React.ReactNode;
initialFlagsData?: SerializedFlagMetadata[];
visitorData?: VisitorData;
}) {
return (
<>
<FlagshipProvider
envId={import.meta.env.VITE_ENV_ID}
apiKey={import.meta.env.VITE_API_KEY}
logLevel={LogLevel.DEBUG}
initialFlagsData={initialFlagsData}
visitorData={visitorData || null}
>
{children}
</FlagshipProvider>
</>
);
}
In the root Layout component, use this provider with the data from the loader:
// In root.tsx Layout component
export function Layout({children}: {children?: React.ReactNode}) {
const nonce = useNonce();
const data = useRouteLoaderData<RootLoader>('root');
return (
<html lang="en">
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link rel="stylesheet" href={tailwindCss}></link>
<link rel="stylesheet" href={resetStyles}></link>
<link rel="stylesheet" href={appStyles}></link>
<Meta />
<Links />
</head>
<body>
<FsProvider
visitorData={data?.fsVisitorData}
initialFlagsData={data?.fsInitialFlags}
>
{data ? (
<Analytics.Provider
cart={data.cart}
shop={data.shop}
consent={data.consent}
>
<PageLayout {...data}>{children}</PageLayout>
</Analytics.Provider>
) : (
children
)}
</FsProvider>
<ScrollRestoration nonce={nonce} />
<Scripts nonce={nonce} />
</body>
</html>
);
}
Use Feature Flags in React Components
Once Flagship is initialized, you can use feature flags in your components with the useFsFlag
hook:
Example 1: Change Text Based on a Flag
// In _index.tsx RecommendedProducts component
function RecommendedProducts({
products,
}: {
products: Promise<RecommendedProductsQuery | null>;
}) {
const headingFlag = useFsFlag('recommended_products_heading');
return (
<div className="recommended-products">
<h2>{headingFlag.getValue('Recommended Products')}</h2>
{/* ... */}
</div>
);
}
Example 2: Conditionally Display Content Based on a Flag
// In ProductItem.tsx
export function ProductItem({
product,
loading,
}: {
product:
| CollectionItemFragment
| ProductItemFragment
| RecommendedProductFragment;
loading?: 'eager' | 'lazy';
}) {
// ...other code
const discountFlag = useFsFlag('show_discount_message'); // Flag for discount
return (
<Link
className="product-item"
key={product.id}
prefetch="intent"
to={variantUrl}
>
{/* ... other content */}
{discountFlag.getValue(true) && (
<div className="discount-message">
Special discount available!
</div>
)}
</Link>
);
}
Configure Content Security Policy for Flagship
When using Flagship with Hydrogen, you need to configure the Content Security Policy (CSP) to allow connections to Flagship's domains. This is done in entry.server.tsx:
// In entry.server.tsx
export default async function handleRequest(
request: Request,
responseStatusCode: number,
responseHeaders: Headers,
reactRouterContext: EntryContext,
context: AppLoadContext,
) {
const {nonce, header, NonceProvider} = createContentSecurityPolicy({
shop: {
checkoutDomain: context.env.PUBLIC_CHECKOUT_DOMAIN,
storeDomain: context.env.PUBLIC_STORE_DOMAIN,
},
connectSrc: ['https://*.flagship.io'], // Allow connections to Flagship domains
});
// ... rest of the server rendering logic
}
Managing Bucketing Data
For optimal performance, Flagship uses bucketing data to make decisions locally without API calls. You can manage this data in several ways:
Development Approach
During development, you can use a static bucketing file:
Fetch the bucketing data from Flagship CDN:
# Replace YOUR_ENV_ID with your Flagship Environment ID
curl -s https://cdn.flagship.io/YOUR_ENV_ID/bucketing.json > app/helpers/bucketing.json
Import it in your code:
import initialBucketing from './bucketing.json';
Production Approach
For production environments, it's better to trigger a redeployment when campaigns are updated rather than committing changes to your repository:
Create a GitHub Action workflow file (
.github/workflows/update-and-deploy.yml
):
name: Update Flagship Bucketing Data and Deploy
on:
# Webhook from Flagship when campaigns change
repository_dispatch:
types: [flagship-campaign-updated]
# Allow manual triggering
workflow_dispatch:
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'yarn'
- name: Install dependencies
run: yarn install --frozen-lockfile
- name: Fetch latest bucketing data
run: |
curl -s https://cdn.flagship.io/${{ secrets.FLAGSHIP_ENV_ID }}/bucketing.json > app/helpers/bucketing.json
- name: Build application
run: yarn build
# For Shopify Oxygen deployment
- name: Deploy to Shopify Oxygen
run: |
npx shopify hydrogen deploy
env:
SHOPIFY_HYDROGEN_DEPLOYMENT_TOKEN: ${{ secrets.SHOPIFY_HYDROGEN_DEPLOYMENT_TOKEN }}
Set up a webhook in the Flagship Platform that triggers this workflow when campaigns are updated. This approach:
Avoids cluttering your commit history with data changes Provides immediate updates to production when campaigns change Follows infrastructure-as-code best practices Works well with modern deployment platforms like Shopify Oxygen
⚠️ Note: If you're using a different hosting platform, replace the deployment step with the appropriate commands for your platform (e.g., Vercel, Netlify, AWS).
Learn More
Last updated
Was this helpful?