Expo Router Setup
This guide covers setting up Storybook as a route inside an Expo Router app. This approach renders Storybook within your app's navigation — useful if you want Storybook accessible alongside your app screens during development.
For most projects, the simpler approach is entry-point swapping — the bundler swaps your entire app entry point for Storybook when STORYBOOK_ENABLED=true is set. No route setup needed, no Storybook code in production. See the Getting Started guide.
The Expo Router approach documented here is fully supported but not the preferred setup, because it embeds Storybook into your app's bundle and navigation.
Metro Configuration
Generate a metro config if you don't have one:
npx expo@latest customize metro.config.js
Since this approach renders Storybook inside your app (not as a separate entry point), use the Metro-specific withStorybook wrapper with the enabled option:
// metro.config.js
const { getDefaultConfig } = require('expo/metro-config');
// Use the Metro-specific wrapper for route-based setup
const { withStorybook } = require('@storybook/react-native/metro/withStorybook');
/** @type {import('expo/metro-config').MetroConfig} */
const config = getDefaultConfig(__dirname);
module.exports = withStorybook(config, {
enabled: true,
});
The bundler-agnostic withStorybook from @storybook/react-native/withStorybook performs entry-point swapping, which replaces your entire app with Storybook. For the Expo Router route approach, you need the Metro-specific wrapper from @storybook/react-native/metro/withStorybook so your app's routing stays intact.
Creating the Storybook Route
Create a route file that sets up the Storybook UI directly. You'll import view from the generated storybook.requires file in your .rnstorybook folder:
app/storybook.tsx
import AsyncStorage from '@react-native-async-storage/async-storage';
import { view } from '../.rnstorybook/storybook.requires';
const StorybookUIRoot = view.getStorybookUI({
storage: {
getItem: AsyncStorage.getItem,
setItem: AsyncStorage.setItem,
},
});
export default StorybookUIRoot;
Navigation Setup
Add navigation to your Storybook route. You can do this through tab navigation, stack navigation, or a dev menu.
Recommended Route Configuration
For the best Storybook experience, disable the header for the Storybook route in your root layout:
app/_layout.tsx
import { Stack } from 'expo-router';
export default function RootLayout() {
return (
<Stack>
<Stack.Screen name="index" />
<Stack.Screen name="storybook" options={{ headerShown: false }} />
</Stack>
);
}
Development-Only Access (Protected Routes)
For production builds, you may want to hide the Storybook route entirely. You can use Expo Router's protected routes feature to only show Storybook in development mode:
app/_layout.tsx
import { Stack } from 'expo-router';
const isDevelopment = __DEV__ || process.env.NODE_ENV === 'development';
export default function RootLayout() {
return (
<Stack>
<Stack.Screen name="index" />
<Stack.Protected guard={isDevelopment}>
<Stack.Screen name="storybook" options={{ headerShown: false }} />
</Stack.Protected>
</Stack>
);
}
This ensures that the Storybook route is only available during development and won't be accessible in production builds.
Running Your App
Once set up, start your app and navigate to the /storybook route:
npm run start
npm run ios # or npm run android
Navigate to /storybook in your app to view your stories.
Video Tutorial
For a visual walkthrough of this setup process, watch this video tutorial: