Skip to main content

Sharing and Deploying Storybook

Storybook is excellent for gathering feedback and ensuring components work correctly across platforms. This guide shows how to share your Storybook with team members.

iOS Distribution via TestFlight

TestFlight enables sharing with up to 10,000 internal testers without rebuilding for each new team member.

Why TestFlight?

Traditional ad hoc provisioning requires:
  • Rebuilding for each new team member
  • Devices in developer mode
  • Manual UDID registration
TestFlight eliminates these requirements by using App Store distribution.
Create a separate app in App Store Connect specifically for Storybook preview builds. This app won’t ship to the App Store.

Setup Guide

1

Create a new Expo app (if needed)

npx create-expo-app --template expo-template-storybook@next AwesomeStorybook
cd AwesomeStorybook
2

Configure EAS

Install EAS CLI and initialize the project:
npm install -g eas-cli
eas login
eas init
eas build:configure -p all
eas update:configure
3

Configure eas.json for Storybook builds

Add a storybook build profile:
{
  "build": {
    "storybook": {
      "distribution": "store",
      "channel": "storybook",
      "autoIncrement": true,
      "ios": {
        "simulator": false
      },
      "env": {
        "EXPO_PUBLIC_ENVIRONMENT": "storybook",
        "EXPO_PUBLIC_STORYBOOK_ENABLED": "true"
      }
    }
  },
  "submit": {
    "storybook": {
      "ios": {
        "bundleIdentifier": "com.example.myapp-storybook",
        "appName": "My App Storybook"
      }
    }
  }
}
Replace com.example.myapp-storybook with your own bundle identifier. Use a different identifier than your production app.
4

Create app.config.ts

Dynamically set bundle identifier based on environment:
// app.config.ts
import { type ExpoConfig, ConfigContext } from 'expo/config';

function getBundleIdentifier() {
  const isStorybook = process.env.EXPO_PUBLIC_ENVIRONMENT === 'storybook';

  if (isStorybook) {
    return 'com.example.myapp.storybook';
  }

  return 'com.example.myapp';
}

function config({ config }: ConfigContext): Partial<ExpoConfig> {
  return {
    // Config from app.json
    ...config,
    ios: {
      ...config.ios,
      bundleIdentifier: getBundleIdentifier(),
      infoPlist: {
        ...config.ios?.infoPlist,
        ITSAppUsesNonExemptEncryption: false,
      },
    },
  };
}

export default config;
5

Build and submit to TestFlight

eas build -p ios --submit --profile storybook
You’ll receive an email when the app is ready for TestFlight distribution.
6

Invite testers

In App Store Connect:
  1. Navigate to TestFlight
  2. Add internal or external testers
  3. Testers receive email invitations
  4. They can install via the TestFlight app

Updating Storybook via OTA

After initial TestFlight distribution, push updates without rebuilding:
# Make changes to your stories
eas update --branch storybook --message "Updated Button component"
Users get updates on next app launch.

Android Distribution

Internal Testing (Play Store)

1

Update eas.json

Same configuration as iOS, but ensure Android package name is set:
{
  "submit": {
    "storybook": {
      "android": {
        "applicationId": "com.example.myapp.storybook"
      }
    }
  }
}
2

Update app.config.ts

Add Android package configuration:
function config({ config }: ConfigContext): Partial<ExpoConfig> {
  return {
    ...config,
    android: {
      ...config.android,
      package: getBundleIdentifier(),
    },
  };
}
3

Build and submit

eas build -p android --submit --profile storybook

APK Distribution (Internal Sharing)

For teams not using Play Store:
{
  "build": {
    "storybook-internal": {
      "extends": "storybook",
      "distribution": "internal",
      "android": { "buildType": "apk" }
    }
  }
}
Build the APK:
eas build -p android --profile storybook-internal
Share the download link from the EAS build page: Android APK download

Web Deployment

Storybook UI is compatible with React Native Web. Deploy to the web for easy access:
1

Build for web

EXPO_PUBLIC_ENVIRONMENT=storybook npx expo export --platform web
This generates static files in dist/.
2

Deploy with EAS

eas deploy
Expo provides a hosted URL for your Storybook.
3

Access your Storybook

Visit the URL provided by EAS (e.g., https://yourproject--hash.expo.app/).

Alternative Web Hosting

# Build
EXPO_PUBLIC_ENVIRONMENT=storybook npx expo export --platform web

# Deploy
netlify deploy --dir=dist --prod

CI/CD Integration

Automate builds and deployments:

GitHub Actions Example

name: Deploy Storybook

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: 18
      
      - name: Install dependencies
        run: npm install
      
      - name: Build Storybook for web
        run: EXPO_PUBLIC_ENVIRONMENT=storybook npx expo export --platform web
      
      - name: Deploy to EAS
        env:
          EXPO_TOKEN: ${{ secrets.EXPO_TOKEN }}
        run: eas deploy

Best Practices

Separate Bundle Identifiers: Always use different bundle identifiers/package names for Storybook preview builds vs. production apps.
Version Control: Use EAS channels (storybook, production) to manage different build variants.
OTA Updates: Leverage EAS Updates for quick iterations without rebuilding native binaries.
Environment Variables: Use EXPO_PUBLIC_STORYBOOK_ENABLED to toggle Storybook on/off. Combine with Metro’s enabled option to exclude Storybook from production bundles.

Troubleshooting

TestFlight Build Missing

  • Verify bundle identifier is unique and registered in App Store Connect
  • Check that ITSAppUsesNonExemptEncryption: false is set in infoPlist
  • Review build logs in EAS dashboard

Web Build Fails

  • Ensure all dependencies are React Native Web compatible
  • Check for native-only code in stories (use platform-specific files if needed)
  • Verify Metro config doesn’t interfere with web builds

OTA Updates Not Appearing

  • Confirm app and update are on the same channel
  • Check update message in EAS dashboard
  • Force close and reopen the app
  • Verify runtimeVersion policy in eas.json

Build docs developers (and LLMs) love