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

# Introduction

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](https://blog.spirokit.com/using-storybook-with-expo-router-v2-sdk-49-typescript)

You can also fork my [GitHub repo](https://github.com/mauro-codes/expo-router-storybook-starter)

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](http://spirokit.com) includes an interactive documentation portal that is built with Storybook and is publicly available [here](http://docs.spirokit.com). Feel free to explore it for inspiration and gather some ideas.

---

# Adding dependencies

## TLDR

1. Update your `package.json` to include all the required and recommended dependencies
    
  ```diff
  {
    "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"
  + },
  }
  ```
    
2. 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](https://storybook.js.org/addons/@storybook/addon-links) (optional)
    
    * This addon can be used to create links that navigate between stories in Storybook.
        
* [@storybook/addon-essentials](https://storybook.js.org/integrations/tag/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:
        
        * [Docs](https://storybook.js.org/docs/react/writing-docs/introduction)
            
        * [Controls](https://storybook.js.org/docs/react/essentials/controls)
            
        * [Actions](https://storybook.js.org/docs/react/essentials/actions)
            
        * [Viewport](https://storybook.js.org/docs/react/essentials/viewport)
            
        * [Backgrounds](https://storybook.js.org/docs/react/essentials/backgrounds)
            
        * [Toolbars & globals](https://storybook.js.org/docs/react/essentials/toolbars-and-globals)
            
        * [Measure & outline](https://storybook.js.org/docs/react/essentials/measure-and-outline)
            
        * [Highlight](https://storybook.js.org/docs/react/essentials/highlight)
            
    * [@storybook/addon-react-native-web](https://storybook.js.org/addons/@storybook/addon-react-native-web) **(Required)**
        
        * This addon configures `@storybook/react` to display React Native (RN) projects using React Native for Web (RNW)
            
    * [@storybook/builder-webpack5](https://www.npmjs.com/package/@storybook/builder-webpack5) & @storybook/manager-webpack5 **(Required)**
        
        * Builder implemented with webpack5 to spin up a dev environment, compile your code into an executable bundle, and update the browser in real-time.
            
    * [@storybook/react](https://www.npmjs.com/package/@storybook/react) **(Required)**
        
        * Storybook React renderer for the web
            
    * Babel plugins & presets **(Required)**
        
        * [babel-plugin-react-docgen-typescript](https://www.npmjs.com/package/babel-plugin-react-docgen-typescript): Plugin to generate docgen data from React components written in TypeScript.
            
        * [babel-plugin-react-native-web:](https://www.npmjs.com/package/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:](https://www.npmjs.com/package/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](https://storybook.js.org/docs/react/builders/vite)

---

# Refactoring our project structure

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:

```plaintext
/.storybook
-- (Configs for native)
-- stories
```

We'll have something like this:

```plaintext
/.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)
```

1. Run the following command to create the required folders
    
    ```bash
    mkdir .storybook/web .storybook/native
    ```
    
2. Move the existing files inside the `native` folder
    
    ```bash
    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
    ```
    
3. In the `native` folder, we need to update the `main.ts` file to look into the right paths:
    
  ```diff
  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",
    ],
  };
  ```
    
4. Create the `main.ts` and `preview.ts` files inside the `web` folder
    
    ```bash
    touch .storybook/web/main.ts .storybook/web/preview.ts
    ```

---

# Storybook Web Configurations

## Adding config files

As I mentioned earlier, we need a different configuration for Storybook Web.

1. Let's start by creating the `main.ts` and `preview.ts` files inside the `.storybook/web` directory:
    
    ```bash
    touch .storybook/web/main.ts
    touch .storybook/web/preview.ts
    ```
    
2. Then, add the following content to the `main.ts` file:
    
    ```bash
    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.
        
3. 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.
    
    ```bash
    export const parameters = {
      actions: { argTypesRegex: "^on[A-Z].*" },
      controls: {
        matchers: {
          color: /(background|color)$/i,
          date: /Date$/,
        },
      },
    };
    ```
    

## Setup babel to generate docs for TypeScript components

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:

```diff
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" }],
    ],
  };
};
```

## Adding NPM Scripts for Storybook web

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. 🙃

```diff
{
  "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": {
    ...
  },
  ...
}
```

---

# Final touch

Go to the `app/index.tsx` file and update the require statement to point to the new `native` folder like this

```diff
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.

```diff
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!

---

# Recap

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!
