Migration Steps
storybook package (core)@storybook/react package@storybook/react-native package@storybook/addon-ondevice-* packagesYou can check the correct version by looking at the
peerDependencies. Please refer to the core Storybook migration guide for more details on the breaking changes in Storybook core v10.{
"devDependencies": {
"@storybook/react-native": "^10.0.0",
"@storybook/react": "^10.0.0",
"@storybook/addon-ondevice-controls": "^10.0.0",
"@storybook/addon-ondevice-actions": "^10.0.0",
"@storybook/addon-ondevice-backgrounds": "^10.0.0",
"@storybook/addon-ondevice-notes": "^10.0.0",
"storybook": "^10.0.0"
}
}
withStorybook is now a named export instead of a default exportwithStorybookConfig (from metro/withStorybookConfig) was a preview of the simplified API. In v10, this is now the standard behavior when importing withStorybook from metro/withStorybookonDisabledRemoveStorybook is removed - When enabled: false, Storybook is automatically removed from the bundle (no separate flag needed)The
withStorybook function is now a named export. You must use const { withStorybook } = require(...) instead of a default import.const { withStorybookConfig } = require('@storybook/react-native/metro/withStorybookConfig');
module.exports = withStorybookConfig(defaultConfig);
const withStorybook = require('@storybook/react-native/metro/withStorybook'); // ❌ Default export
module.exports = withStorybook(defaultConfig, {
enabled: process.env.STORYBOOK_ENABLED === 'true',
onDisabledRemoveStorybook: true, // This option no longer exists in v10
configPath: path.resolve(__dirname, './.rnstorybook'),
});
const { withStorybook } = require('@storybook/react-native/metro/withStorybook'); // ✅ Named export
// Basic usage - works out of the box with defaults
module.exports = withStorybook(defaultConfig);
// Or with options
module.exports = withStorybook(defaultConfig, {
// When false, automatically removes Storybook from bundle
enabled: process.env.EXPO_PUBLIC_STORYBOOK_ENABLED === 'true',
configPath: path.resolve(__dirname, './.rnstorybook'),
});
const { withStorybook } = require(...) instead of default importwithStorybookConfig functionality is now the default behavior of withStorybookenabled: false, Storybook packages are automatically stubbed out (no need for onDisabledRemoveStorybook)configPath is ./.rnstorybookIn v10, you can now directly import and export Storybook without worrying about bundle size or conditional imports. The metro config automatically handles everything.
You couldn’t safely import Storybook at the top level because it would be included in your production bundle, even when disabled. Then even if you used the onDisabledRemoveStorybook option you would still need to conditionally import Storybook in your app code to not cause a crash.
// App.tsx
let AppEntryPoint = App;
if (process.env.EXPO_PUBLIC_STORYBOOK_ENABLED === 'true') {
// Conditional import to avoid bundling Storybook in production
AppEntryPoint = require('./.rnstorybook').default;
}
export default AppEntryPoint;
Now you can safely import Storybook at the top level - metro will automatically remove it when
enabled: false:// App.tsx
import StorybookUI from './.rnstorybook'; // ✅ Safe! Metro stubs this out when enabled: false
import { Text, View } from 'react-native';
const isStorybook = process.env.EXPO_PUBLIC_STORYBOOK_ENABLED === 'true';
// this could be your app entrypoint
const AppComponent = () => {
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Text>Hello World</Text>
</View>
);
};
export default function App() {
return isStorybook ? <StorybookUI /> : <AppComponent />;
}
The key difference: the top-level
import is now safe! Metro automatically stubs out the .rnstorybook import when enabled: false, removing all Storybook code from your production bundle.Or even simpler, with Expo Router, you can create a dedicated route and now not worry about conditional inline requires.
enabled: true - Storybook is included in the bundleenabled: false - The .rnstorybook import is automatically stubbed out as an empty module, and all Storybook dependencies are removed/storybook routeAfter updating dependencies and configuration, regenerate your
.rnstorybook/storybook.requires.ts file:Summary of Breaking Changes
-
Metro config API changes:
- BREAKING:
withStorybookis now a named export - useconst { withStorybook } = require(...)instead of default import withStorybookConfigfrommetro/withStorybookConfigis removed- Use
withStorybookfrommetro/withStorybookinstead (the simplified API is now standard) onDisabledRemoveStorybookoption removed (automatic whenenabled: false)
- BREAKING:
-
Default config folder:
- Confirmed as
./.rnstorybook(to avoid conflicts with web Storybook)
- Confirmed as
-
Storybook core updated to v10:
- See Storybook v10 migration guide for core breaking changes
-
Simplified app entry:
- Custom switcher components no longer needed for bundle optimization
- Metro config handles conditional inclusion automatically