Setting Up Storybook Web and Native with Expo Router v2, SDK 49, and TypeScript

Search for a command to run...

Awesome, was kinda hoping for Storybook 7 support, but this will do nicely!
Yes, you are not alone. V7 is not supported on React Native yet. But I will update this guide as soon as it’s released.
Glad it was useful 😄 I know I wanted to find this article a year ago and I couldn’t, so I had to write it 😂
No problem 🙂 Thank you for let it me know 🙌
💡 This is the third part of the "Collecting payments" series. Before proceeding, make sure to read Part 1, Part 2 & Part 3 We've finally reached the last part of the series! With all the tedious aspects taken care of, we just need to connect everyt...

💡 This is the third part of the "Collecting payments" series. Before proceeding, make sure to read Part 1 and Part 2 Before diving into this third part, I highly recommend watching this video to become familiar with some essential RevenueCat conce...

💡 This is the third part of the "Collecting payments" series. Before proceeding, make sure to read Part 1 SpiroKit for SaaS In case you want to save weeks of painful hours of work & research on your next React Native app, checkout SpiroKit for Sa...

Introduction Creating a mobile app involves numerous challenges and time-consuming tasks that must be completed before we can release our app in the stores. In-app purchases and subscriptions are especially difficult examples of this. They require co...

Building apps take time Building a mobile app is a challenging task. From coming up with an idea, prototyping, and finally building the app, there are many things to consider and problems we'll have to figure out: How do we make sure the entire app ...

In my previous post, I shared a comprehensive guide about setting up Storybook to work with Expo SDK 49, Expo Router v2, and TypeScript.
If you haven't read that article yet, I highly recommend checking it out, as I'll be using the repository from the last post as a starting point.
Using Storybook with Expo Router v2, SDK 49 & TypeScript
You can also fork my GitHub repo
Since the Storybook Native setup is already working with Expo SDK 49, Expo Router v2, and TypeScript on my repo, this article will focus on adding Storybook web, so we can easily share our components with the rest of the team, or even publish it with Vercel.
The SpiroKit UI Kit includes an interactive documentation portal that is built with Storybook and is publicly available here. Feel free to explore it for inspiration and gather some ideas.
Update your package.json to include all the required and recommended dependencies
{
"scripts": {
...
},
"dependencies": {
...
},
"devDependencies": {
"@babel/core": "^7.20.0",
"typescript": "^5.1.3",
"@types/react": "~18.2.14",
"@babel/plugin-proposal-export-namespace-from": "^7.18.9",
"@react-native-async-storage/async-storage": "^1.19.0",
"@react-native-community/datetimepicker": "^7.4.0",
"@react-native-community/slider": "^4.4.2",
"@storybook/addon-actions": "^6.5.16",
"@storybook/addon-controls": "^6.5.16",
+ "@storybook/addon-essentials": "^6.5.16",
+ "@storybook/addon-links": "^6.5.16",
"@storybook/addon-ondevice-actions": "^6.5.4",
"@storybook/addon-ondevice-controls": "^6.5.4",
+ "@storybook/addon-react-native-web": "^0.0.21",
"@storybook/react-native": "^6.5.4",
+ "@storybook/react": "^6.5.16",
+ "@storybook/builder-webpack5": "^6.5.14",
+ "@storybook/manager-webpack5": "^6.5.14",
+ "babel-plugin-react-docgen-typescript": "^1.5.1",
+ "babel-plugin-react-native-web": "^0.18.10",
+ "metro-react-native-babel-preset": "^0.77.0",
"babel-loader": "^8.3.0"
},
"expo": {
...
},
+ "resolutions": {
+ "react-docgen-typescript": "2.2.2",
+ "@storybook/react-docgen-typescript-plugin": "1.0.6--canary.9.cd77847.0"
+ },
+ "overrides": {
+ "react-docgen-typescript": "2.2.2",
+ "@storybook/react-docgen-typescript-plugin": "1.0.6--canary.9.cd77847.0"
+ },
}
Run npm install
For the web setup, we need to add a few dependencies. Some are required, and some are optional (but recommended for a better experience).
@storybook/addon-links (optional)
@storybook/addon-essentials (optional, but highly recommended)
Storybook ships by default with a set of “essential” addons that add to the initial user experience. There are many third-party add-ons as well as “official” add-ons developed by the Storybook core team, such as:
@storybook/addon-react-native-web (Required)
@storybook/react to display React Native (RN) projects using React Native for Web (RNW)@storybook/builder-webpack5 & @storybook/manager-webpack5 (Required)
@storybook/react (Required)
Babel plugins & presets (Required)
babel-plugin-react-docgen-typescript: Plugin to generate docgen data from React components written in TypeScript.
babel-plugin-react-native-web: Plugin that will alias react-native to react-native-web and exclude any modules not required by your app (keeping bundle size down).
metro-react-native-babel-preset: Babel preset for React Native applications. React Native itself uses this Babel preset by default when transforming your app's source code.
Storybook also allows you to use Vite instead of Webpack, but I didn't try it yet. If you are interested on that kind of setup, check out this link
After finishing the previous post, you'll have a .storybook folder in the root of your project, containing all the config files for Storybook Native. However, since Storybook Web uses different plugins and addons, we'll need to create separate main.ts and preview.ts files.
So, instead of having this project structure:
/.storybook
-- (Configs for native)
-- stories
We'll have something like this:
/.storybook
-- stories
-- web (Folder for the web setup)
---- main.ts
---- preview.ts
-- native (Folder for the native setup)
---- index.ts (exports the Storybook UI)
---- main.ts
---- preview.ts
---- storybook.requires.js (autogenerated)
Run the following command to create the required folders
mkdir .storybook/web .storybook/native
Move the existing files inside the native folder
mv .storybook/index.ts .storybook/native/index.ts
mv .storybook/main.ts .storybook/native/main.ts
mv .storybook/preview.ts .storybook/native/preview.ts
mv .storybook/storybook.requires.js .storybook/native/storybook.requires.js
In the native folder, we need to update the main.ts file to look into the right paths:
module.exports = {
- stories: ["./stories/**/*.stories.?(ts|tsx|js|jsx)"],
+ stories: ["../stories/**/*.stories.?(ts|tsx|js|jsx)"],
addons: [
"@storybook/addon-ondevice-controls",
"@storybook/addon-ondevice-actions",
],
};
Create the main.ts and preview.ts files inside the web folder
touch .storybook/web/main.ts .storybook/web/preview.ts
As I mentioned earlier, we need a different configuration for Storybook Web.
Let's start by creating the main.ts and preview.ts files inside the .storybook/web directory:
touch .storybook/web/main.ts
touch .storybook/web/preview.ts
Then, add the following content to the main.ts file:
module.exports = {
stories: [
"../../src/components/**/*.stories.mdx",
"../../src/components/**/*.stories.@(js|jsx|ts|tsx)",
],
addons: [
"@storybook/addon-links",
"@storybook/addon-essentials",
"@storybook/addon-react-native-web",
],
core: {
builder: "webpack5",
},
framework: "@storybook/react",
};
Here are a few things to mention about this configuration:
It loads stories from the src/components directory with MDX, JS, JSX, TS, and TSX file extensions.
Includes essential add-ons, links, and React Native Web support.
Includes Webpack 5 setup to be used as the builder and set the framework to Storybook React.
Finally, add the following content to the preview.ts file. Although this file is the same for both Web and Mobile at the moment, it's ok to keep these files separated. That way, we can apply different customizations for each platform as needed.
export const parameters = {
actions: { argTypesRegex: "^on[A-Z].*" },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
};
We need to add the babel-plugin-react-docgen-typescript plugin in our babel.config.js file, so we can get useful generated docs for our TypeScript components:
module.exports = function (api) {
api.cache(true);
return {
presets: ["babel-preset-expo"],
plugins: [
"react-native-reanimated/plugin",
require.resolve("expo-router/babel"),
+ ["babel-plugin-react-docgen-typescript", { exclude: "node_modules" }],
],
};
};
Remember how running npm start launches our native app? I wanted to add two more commands: one to run Storybook Web and another to build the web portal. The latter will be useful if you plan to deploy it to Vercel later.
Note that I also had to update the npm start command to point to the new config path. Make sure to check that as well; otherwise, you'll get an error. 🙃
{
"scripts": {
- "start": "sb-rn-get-stories && expo start",
+ "start": "sb-rn-get-stories --config-path .storybook/native && expo start",
"android": "expo start --android",
"ios": "expo start --ios",
"web": "expo start --web",
"storybook-generate": "sb-rn-get-stories",
"storybook-watch": "sb-rn-watcher",
+ "storybook:web": "start-storybook --config-dir .storybook/web -p 6006",
+ "build-storybook": "build-storybook"
},
"dependencies": {
...
},
"devDependencies": {
...
},
...
}
Go to the app/index.tsx file and update the require statement to point to the new native folder like this
import React from "react";
import { Text, View } from "react-native";
import { SafeAreaView } from "react-native-safe-area-context";
const storybookEnabled = process.env.EXPO_PUBLIC_STORYBOOK_ENABLED === "true";
const Index = () => {
return (
<SafeAreaView>
<Text>Hello world</Text>
</SafeAreaView>
);
};
let EntryPoint = Index;
if (storybookEnabled) {
- const StorybookUI = require("../.storybook").default;
+ const StorybookUI = require("../.storybook/native").default;
EntryPoint = () => {
return (
<View style={{ flex: 1 }}>
<StorybookUI />
</View>
);
};
}
export default EntryPoint;
Last but not least, add the storybook-static directory to your .gitignore to prevent static files from being included in your source control.
node_modules/
.expo/
dist/
npm-debug.*
*.jks
*.p8
*.p12
*.key
*.mobileprovision
*.orig.*
web-build/
+ storybook-static/
# macOS
.DS_Store
That's it! Now run npm run storybook:web and enjoy!
In this article, we installed additional dependencies and dealt with Babel configurations to successfully run Storybook web with our React Native components.
I love that everything related to Storybook configuration now resides in the .storybook folder, serving as a single source of truth (including the stories).
Now, you can write your stories once and run Storybook on both native and web platforms. 🪄
Additionally, you can publish your Storybook Web portal to Vercel using the build script.
If you found this article helpful, please let me know in the comments section or hit the like button, so I'll continue writing about this topic.
Happy coding!