Gatsby
Complete guide for integrating Supaship feature flags with Gatsby, including SSR, static generation, and client-side rendering support.
Requirements
- Gatsby 3.0+
- React 16.8+ (for hooks support)
- Use
@supashiphq/react-sdkpackage
Installation
pnpm add @supashiphq/react-sdkQuick Start
Setup Provider
// src/providers.tsx
import { SupaProvider, FeaturesWithFallbacks, InferFeatures } from '@supashiphq/react-sdk'
const FEATURE_FLAGS = {
'new-homepage': false,
'theme-config': { mode: 'light' as const, showLogo: true },
'beta-features': [] as string[],
} satisfies FeaturesWithFallbacks
// REQUIRED: for type safety
declare module '@supashiphq/react-sdk' {
interface Features extends InferFeatures<typeof FEATURE_FLAGS> {}
}
export function Providers({ children }: { children: React.ReactNode }) {
return (
<SupaProvider
config={{
apiKey: process.env.GATSBY_SUPASHIP_API_KEY!,
environment: process.env.NODE_ENV!,
features: FEATURE_FLAGS,
context: {
version: process.env.GATSBY_APP_VERSION,
},
}}>
{children}
</SupaProvider>
)
}Wrap Root Element
// gatsby-ssr.tsx and gatsby-browser.tsx
import React from 'react'
import { Providers } from './src/providers'
export const wrapRootElement = ({ element }) => {
return <Providers>{element}</Providers>
}Using in Components
// src/pages/index.tsx
import { useFeature } from '@supashiphq/react-sdk'
export default function HomePage() {
const { feature: newHomepage, isLoading } = useFeature('new-homepage')
if (isLoading) return <div>Loading...</div>
return <main>{newHomepage ? <NewHomepage /> : <OldHomepage />}</main>
}Environment Variables
Create a .env.development and .env.production file:
GATSBY_SUPASHIP_API_KEY=your-api-key-here
GATSBY_APP_VERSION=1.0.0Important: In Gatsby, environment variables prefixed with GATSBY_ are exposed to the browser. Never expose sensitive keys without the prefix.
Type-Safe Feature Flags
Centralize your feature definitions:
// src/lib/features.ts
import { FeaturesWithFallbacks, InferFeatures } from '@supashiphq/react-sdk'
export const FEATURE_FLAGS = {
'new-dashboard': false,
'theme-config': {
mode: 'light' as 'light' | 'dark',
primaryColor: '#007bff',
showLogo: true,
},
'beta-features': [] as string[],
'disabled-feature': null,
} satisfies FeaturesWithFallbacks
// Type augmentation for global type safety
declare module '@supashiphq/react-sdk' {
interface Features extends InferFeatures<typeof FEATURE_FLAGS> {}
}Then use in your provider:
// src/providers.tsx
import { FEATURE_FLAGS } from './lib/features'
export function Providers({ children }: { children: React.ReactNode }) {
return (
<SupaProvider
config={{
apiKey: process.env.GATSBY_SUPASHIP_API_KEY!,
environment: process.env.NODE_ENV!,
features: FEATURE_FLAGS,
}}>
{children}
</SupaProvider>
)
}Patterns
Server-Side Rendering (SSR)
Gatsby uses SSR for certain pages. Feature flags work seamlessly:
// src/pages/about.tsx
import { useFeature } from '@supashiphq/react-sdk'
export default function AboutPage() {
const { feature: showNewAbout } = useFeature('new-about-page')
return (
<div>
{showNewAbout ? <NewAboutContent /> : <OldAboutContent />}
</div>
)
}Static Site Generation (SSG)
For static pages, feature flags are evaluated at build time and can be updated client-side:
// src/pages/products.tsx
import { useFeatures } from '@supashiphq/react-sdk'
export default function ProductsPage() {
const { features, isLoading } = useFeatures(['new-product-layout', 'show-reviews'])
if (isLoading) return <LoadingSpinner />
return (
<div>
{features['new-product-layout'] ? (
<NewProductLayout showReviews={features['show-reviews']} />
) : (
<OldProductLayout />
)}
</div>
)
}Client-Side Only Features
For features that should only be evaluated client-side:
// src/components/ClientOnlyFeature.tsx
import { useEffect, useState } from 'react'
import { useFeature } from '@supashiphq/react-sdk'
export function ClientOnlyFeature() {
const [isClient, setIsClient] = useState(false)
const { feature } = useFeature('client-only-feature', {
shouldFetch: isClient,
})
useEffect(() => {
setIsClient(true)
}, [])
if (!isClient) return null
return <div>{feature && <FeatureContent />}</div>
}Dynamic Context Updates
Update context when user state changes:
// src/components/UserProvider.tsx
import { useEffect } from 'react'
import { useFeatureContext } from '@supashiphq/react-sdk'
import { useAuth } from './hooks/useAuth'
export function UserProvider({ children }: { children: React.ReactNode }) {
const { updateContext } = useFeatureContext()
const { user } = useAuth()
useEffect(() => {
if (user) {
updateContext({
userId: user.id,
email: user.email,
plan: user.plan,
segment: user.segment,
})
}
}, [user, updateContext])
return <>{children}</>
}
// Use in gatsby-browser.tsx
import { UserProvider } from './src/components/UserProvider'
export const wrapRootElement = ({ element }) => {
return (
<Providers>
<UserProvider>{element}</UserProvider>
</Providers>
)
}GraphQL Integration
You can combine feature flags with Gatsby’s GraphQL:
// src/pages/blog.tsx
import { graphql } from 'gatsby'
import { useFeature } from '@supashiphq/react-sdk'
export default function BlogPage({ data }) {
const { feature: newBlogLayout } = useFeature('new-blog-layout')
return (
<div>
{newBlogLayout ? (
<NewBlogLayout posts={data.allMarkdownRemark.nodes} />
) : (
<OldBlogLayout posts={data.allMarkdownRemark.nodes} />
)}
</div>
)
}
export const query = graphql`
query {
allMarkdownRemark {
nodes {
frontmatter {
title
date
}
}
}
}
`Page Templates
Use feature flags in page templates:
// src/templates/blog-post.tsx
import { graphql } from 'gatsby'
import { useFeature } from '@supashiphq/react-sdk'
export default function BlogPostTemplate({ data }) {
const { feature: showComments } = useFeature('blog-comments')
return (
<article>
<h1>{data.markdownRemark.frontmatter.title}</h1>
<div dangerouslySetInnerHTML={{ __html: data.markdownRemark.html }} />
{showComments && <CommentsSection />}
</article>
)
}
export const query = graphql`
query ($id: String!) {
markdownRemark(id: { eq: $id }) {
html
frontmatter {
title
}
}
}
`Advanced Patterns
Build-Time Feature Flags
For features that need to be evaluated at build time:
// gatsby-node.ts
import { createClient } from '@supashiphq/js-sdk'
const client = createClient({
apiKey: process.env.GATSBY_SUPASHIP_API_KEY!,
environment: process.env.NODE_ENV!,
features: FEATURE_FLAGS,
})
export async function createPages({ graphql, actions }) {
const { createPage } = actions
// Check feature flag at build time
const useNewBlog = await client.getFeature('new-blog-system')
if (useNewBlog) {
// Create pages with new blog system
// ...
} else {
// Create pages with old blog system
// ...
}
}Conditional Page Creation
Create pages conditionally based on feature flags:
// gatsby-node.ts
export async function createPages({ graphql, actions }) {
const { createPage } = actions
const client = createClient({
apiKey: process.env.GATSBY_SUPASHIP_API_KEY!,
environment: process.env.NODE_ENV!,
features: FEATURE_FLAGS,
})
const features = await client.getFeatures(['beta-pages', 'preview-mode'])
if (features['beta-pages']) {
// Create beta pages
createPage({
path: '/beta',
component: require.resolve('./src/templates/BetaPage.tsx'),
})
}
}API Routes (Gatsby Functions)
Use feature flags in Gatsby Functions:
// src/api/features.ts
import { createClient } from '@supashiphq/js-sdk'
const client = createClient({
apiKey: process.env.GATSBY_SUPASHIP_API_KEY!,
environment: process.env.NODE_ENV!,
features: FEATURE_FLAGS,
})
export default async function handler(req, res) {
const { userId } = req.query
const features = await client.getFeatures(['feature-a', 'feature-b'], {
context: { userId },
})
res.json(features)
}Best Practices
1. Use Type Augmentation
Always use type augmentation for type safety:
declare module '@supashiphq/react-sdk' {
interface Features extends InferFeatures<typeof FEATURE_FLAGS> {}
}2. Environment-Specific Configuration
// src/lib/features.ts
export const FEATURE_FLAGS = {
'new-dashboard': false,
'beta-mode': process.env.NODE_ENV === 'development',
} satisfies FeaturesWithFallbacks3. Handle Loading States
export default function MyPage() {
const { feature, isLoading } = useFeature('my-feature')
if (isLoading) return <Skeleton />
return <div>{feature ? <NewComponent /> : <OldComponent />}</div>
}4. Batch Feature Requests
// ✅ Good - single API call
const { features } = useFeatures(['feature-1', 'feature-2', 'feature-3'])
// ❌ Less efficient - multiple API calls
const feature1 = useFeature('feature-1')
const feature2 = useFeature('feature-2')
const feature3 = useFeature('feature-3')5. Update Context Reactively
useEffect(() => {
if (user) {
updateContext({
userId: user.id,
email: user.email,
})
}
}, [user, updateContext])Testing
Mock Feature Flags in Tests
// __tests__/setup.tsx
import { SupaProvider, FeaturesWithFallbacks } from '@supashiphq/react-sdk'
export function TestProviders({
children,
features = {},
}: {
children: React.ReactNode
features?: FeaturesWithFallbacks
}) {
return (
<SupaProvider
config={{
apiKey: 'test-key',
environment: 'test',
features: features satisfies FeaturesWithFallbacks,
}}>
{children}
</SupaProvider>
)
}
// __tests__/page.test.tsx
import { render, screen } from '@testing-library/react'
import { TestProviders } from './setup'
import HomePage from '../src/pages/index'
describe('HomePage', () => {
it('shows new homepage when enabled', () => {
render(
<TestProviders features={{ 'new-homepage': true }}>
<HomePage />
</TestProviders>,
)
expect(screen.getByText('New Homepage')).toBeInTheDocument()
})
})Troubleshooting
Provider Not Found Error
Error: useFeature must be used within a SupaProviderSolution: Ensure you’ve wrapped your app in both gatsby-ssr.tsx and gatsby-browser.tsx:
// gatsby-ssr.tsx and gatsby-browser.tsx
export const wrapRootElement = ({ element }) => {
return <Providers>{element}</Providers>
}Environment Variables Not Working
Solution: Ensure variables are prefixed with GATSBY_ and restart the dev server:
# .env.development
GATSBY_SUPASHIP_API_KEY=your-keyHydration Mismatch
Solution: Ensure feature flags are fetched consistently on both server and client, or use client-only features:
import { useEffect, useState } from 'react'
const [isClient, setIsClient] = useState(false)
useEffect(() => {
setIsClient(true)
}, [])
if (!isClient) return nullAPI Reference
For complete API documentation, see the React SDK API Reference.