How to add React Native Web to an existing React Native project

React Native for Web allows you to make your React Native application available for your users through the web.

The official react-native-web docs cover a few different methods like using expo or create-react-app. This post covers the most customizable method of adding web support to a plain React Native app using webpack and react-native-web .

If you prefer watching a video, I’ve gone through the steps below in this 4-minute video:

Alternative Methods

There are a few different ways you can get your React Native project running on the web. This post covers using custom webpack configuration. However, if you’d like to explore a simpler solution with a pre-configured webpack, below you can find some alternative ways:

  • Initialize with Expo or use expo-cli
    If you’ve initialized your project with Expo, it already comes with React Native for Web pre-configured.
    Otherwise, you can find instructions on adding expo-cli to your existing react-native project following the expo examples documentss. Note that according to the instructions the latter is not ready for production.
  • Initialize with create-react-app
    Instead of directly using webpack you can also use create-react-app and react-scripts to build and configure your app. You can follow this great post which describes the process.

Adding React Native Web to a React Native init project

If you’re working with plain react native, below you can find the steps needed to allow your project to also run on the web.

First, initialize your react-native project:

npx react-native init rnw_blogpost --template react-native-template-typescriptOr without typescript:npx react-native init rnw_blogpost

Then, add your new dependencies to support react-native-web :

cd rnw_blogpostyarn add react-native-webyarn add -D babel-plugin-react-native-web webpack webpack-cli webpack-dev-server html-webpack-plugin react-dom babel-loader url-loader @svgr/webpack

The next few steps include adding a few new files: index.html,index.web.tsx,webpack.config.js, and App.web.tsx. You can copy-paste them from this gist or run the following command to download all of the required files from Github.

curl -L https://gist.github.com/arrygoo/81d95ecc55313a7d0668f6711cfc7ff9/download | tar -xvz --strip-components=1

Note: These files are for a TypeScript project. If you’re using plain JavaScript rename App.web.tsx ->App.web.js and its reference in webpack.config.js

Let’s break down these files in more detail below.

index.html:

This is the root of your app, and the main div. React uses this div to populate the page with your components. You can also use this html file to load fonts, set meta tags, and set global css for your app. In our example, we’re just setting some basic styling to make the main div have full height and width.

index.html:
<html>
...
...
<body>
<div id="app-root"></div>
</body>
</html>

index.web.js:

Using index.web.js you can separate the entry between web and mobile. The .web.js extension makes it so that this file is only picked up for the web and not for mobile. Some other available extensions are .native.js,.ios.js, and .android.js for mobile. Or if you prefer to keep both native and web code in one index.jsfile you can use import { Platform } from 'react-native' to separate mobile and web code conditionally. Read more about platform-specific code in the official React Native docs.

index.web.js:
...
...
AppRegistry.runApplication(appName, {
initialProps: {},
rootTag: document.getElementById('app-root'),
});

^ This is very similar to your index.js for mobile, except that it also connects your app to the root div which we defined above in index.html.

webpack.config.js:

The webpack configurations include all of the instructions for serving and building the web app. The configuration below is mostly built on top of the official RN Web documents with some small additions. Below we’ll expand on what each part of the configuration means:

The first step is compiling any native library to the web:

webpack.config.js:
...
...
const compileNodeModules = [
// Add every react-native package that needs compiling
// 'react-native-gesture-handler',
].map((moduleName) => path.resolve(appDirectory, `node_modules/${moduleName}`));

If you add a native library and not add it to this list you’ll likely run into an error like this:

ERROR in ./node_modules/react-native-gesture-handler
Module parse failed: Unexpected token
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders

Then we include all of the files and folders containing the source code. We’re compiling index.web.js ,App.web.tsx , everything under the src folder, and all of the libraries that were compiled in the step above. Make sure to update the filenames according to your project, otherwise, you’ll run into the error above.

webpack.config.js:
...
...
const babelLoaderConfiguration = {
test: /\.js$|tsx?$/,
// Add every directory that needs to be compiled by Babel during the build.
include: [
path.resolve(__dirname, 'index.web.js'), <-- Entry to web project
path.resolve(__dirname, 'App.web.tsx'), // <-- or App.js
path.resolve(__dirname, 'src'), // <-- Main source folder
...compileNodeModules, // <-- Modules we compiles above
],
...
...

Next, we have a few libraries to load png and svg files. We are using url-loader and @svgr/webpack , as well as the babel-loader configuration from the previous step.

webpack.config.js:
...
...
module: {
rules: [
babelLoaderConfiguration,
imageLoaderConfiguration,
svgLoaderConfiguration,
],
},
...
...

Failing to include the svg and png loaders will lead to an error like this:

ERROR in ./logo.png 1:0
Module parse failed: Unexpected character '�' (1:0)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders

Next, we’re adding three plugins.

webpack.config.js:
...
...
plugins: [
new HtmlWebpackPlugin({
template: path.join(__dirname, 'index.html'),
}),
new webpack.HotModuleReplacementPlugin(),
new webpack.DefinePlugin({
// See: https://github.com/necolas/react-native-web/issues/349
__DEV__: process.env.NODE_ENV !== "production",
}),
],
...
...

App.web.tsx:

This file is a temporary addition to verify that our setup is working before migrating the rest of the app to React Native web. Eventually, you can remove this file, and have a shared App.tsx that runs on both web and mobile.

App.web.tsx:
...
...
const App = () => {
const [count, setCount] = useState(0);
return (
<View style={styles.container}>
<TouchableOpacity
onPress={() => setCount(count + 1)}
style={styles.button}>
<Text>Click me!</Text>
</TouchableOpacity>
<Text>You clicked {count} times!</Text>
</View>
);
};
...
...

Note that since this file runs only on the web, you can include any web-only react libraries here. In other words, in your .web.js files you can mix react-native code as well as plain web specific HTML and JavaScript. As an example, this would be a valid .web.js file:

const app () => {
const { domain } = document; // using browser's global document variable
return (
<div>
<Text>Hello!</Text>
</div>
)
}

Notice how we used the browser-specific global variable document, as well as a div . This would fail to run on mobile, but it works just fine in .web files.

package.json changes:

Finally, modify the scripts section under package.json. This step will add the required to build and serve commands.

    "build": "rm -rf dist/ && webpack --mode=production --config webpack.config.js",
"web": "webpack serve --mode=development --config webpack.config.js"

Now you should be able to run your app using either

yarn run web

or if you’d like to deploy it to the web using Vercel:

yarn build
cd dist
vercel

Now if you open your browser to localhost:8080 you should see your web app running like so:

Once you’ve confirmed that your React Native Web setup is working for the very simple component above, you can gradually add your app components to App.web.tsx and test them until all of your apps are ported to the web.

I hope that you find this post useful. Let me know if I’ve missed any points and I’ll make sure to update the blog post with more information.

Software Developer, currently at Shopify

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store