Skip to Content

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-sdk package

Installation

pnpm add @supashiphq/react-sdk

Quick 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.0

Important: 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 FeaturesWithFallbacks

3. 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 SupaProvider

Solution: 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-key

Hydration 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 null

API Reference

For complete API documentation, see the React SDK API Reference.

Last updated on