Build and Deploy a React Native Web App on Netlify from Scratch - howtoexitvim.org
Tags: reactnative, react, tutorial, showdev
Published: 2019-12-18 21:37:00


[React Native Web](https://github.com/necolas/react-native-web) allows developers to write web apps using React Native components and patterns. Unlike regular React components, components written with React Native Web can easily be shared across other platforms that React Native supports, like iOS, Android, and Windows. While tools like [Expo Web](https://docs.expo.io/versions/v36.0.0/bare/using-web/) can make getting started a breeze, the configuration and setup is simple enough that anyone can easily build a React Native Web project from scratch. This article will show exactly how easy it is to setup, build, and deploy a React Native Web app by building the site for how to exit vim from scratch without any template.

[Github](https://github.com/codypearce/howtoexitvim)

- Setup and Configuration
- Hello World with React Native Web
- Building howtoexitvim.org
- Deploying to Netlify

## Setup and Configuration

We're going to setup everything from scratch with the minimum configuration required.

Create a new directory, `howtoexitvim`.

```shell
mkdir howtoexitvim
```

Initialize a `package.json` and change the main entry point to `src/index.js`.

```shell
npm init
```

![npm init](https://www.consolelogs.dev/static/3c6d2fa0dcc1f6143eca64ec9503570a/daba0/npm.png)

React Native Web does not require any more dependencies than a regular React app, aside from the `react-native-web` package itself. Components from `react-native-web` are built with React DOM, so we do not need to install React Native itself for projects that only support web browsers.

We will use Babel to transform our code and Webpack to both serve and bundle the app. Install the following dependencies:

```shell
npm i react react-dom react-native-web webpack webpack-cli webpack-dev-server html-webpack-plugin html-loader babel-loader @babel/core @babel/preset-env @babel/preset-react
```

Next, create `webpack.config.js` at the root of the project with the usual configuration for a React app. Check out this excellent article, [How to configure React with Webpack & Friends from the ground up](https://dev.to/macmacky/how-to-configure-react-with-webpack-friends-from-the-ground-up-2c5g) by Mark A, to learn how each of these sections work.

```javascript
const HtmlWebPackPlugin = require("html-webpack-plugin")

module.exports = {
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        exclude: /node_modules\/(?!()\/).*/,
        use: {
          loader: "babel-loader",
          options: {
            presets: ["@babel/preset-env", "@babel/preset-react"],
          },
        },
      },
      {
        test: /\.html$/,
        use: [
          {
            loader: "html-loader",
          },
        ],
      },
    ],
  },
  plugins: [
    new HtmlWebPackPlugin({
      template: "./public/index.html",
      filename: "./index.html",
    }),
  ],

  devServer: {
    historyApiFallback: true,
    contentBase: "./",
    hot: true,
  },
}
```

Let's also alias `react-native` to `react-native-web` so that when Webpack sees this:

```
import { SomeComponent } from 'react-native'
```

It will instead import the component from `react-native-web`, like this:

```
import { SomeComponent } from 'react-native-web'
```

This saves us the hassle of changing the imports if we use our code on mobile. Add the following between `plugins` and `devServer`.

```javascript
...
resolve: {
    alias: {
      "react-native": "react-native-web"
    },
    extensions: [".web.js", ".js"]
  },
...
```

Finally, create an `npm` script to run the `webpack-dev-server`.

```javascript
...
"scripts": {
    "start": "webpack-dev-server --config ./webpack.config.js --mode development",
  },
...
```

Now that all of the dependencies and configuration is setup, let's create a simple `hello world` app with React Native Web.

## Hello World with React Native Web

When you are done with this section, your folder structure should look like this.

![hello-files](https://www.consolelogs.dev/static/7a91234bab01497567a99b623c0f277f/79c2d/hello-files.png)

First, create a new folder `public` to hold all the static files in the app. Then create a barebones `index.html` file within that folder.

```html
<!DOCTYPE html>
<html>
  <head>
    <title>How To Exit Vim</title>
  </head>
  <body>
    <div id="app"></div>
  </body>
</html>
```

Next, create `src/App.js` with the text "Hello World" using React Native's `<Text>` component.

```jsx
import React from "react"
import ReactDOM from "react-dom"
import { Text } from "react-native"

export default class App extends React.Component {
  render() {
    return <Text>Hello World</Text>
  }
}
```

The last file we'll need is `src/index.js`, which will render the app in the DOM using `react-dom`.

```jsx
import React from "react"
import ReactDOM from "react-dom"

import App from "./App"

ReactDOM.render(<App />, document.getElementById("app"))
```

Finally, run `npm start` in the terminal to run the app. Visit `http://localhost:8080/` to see the the "Hello World".

![Hello World](https://www.consolelogs.dev/static/b3a108346b2e5b21cc3b25d40bf304ec/793c2/helloworld.png)

## Building the site 

The site will display a few commands to exit vim with a short description of what the command does. To accomplish this we will only need four components: `Container`, `Title`, `Escape`, and `Command`. However, before we start building the React Native components, we need to import the fonts faces for the title and content, as well as set the height of body to `100%` so our background will naturally fill the whole page.

### Adding Fonts and Height 100%

Add the following between the `<head>` tags in `public/index.html`:

```html
...
<style>
  @import "https://fonts.googleapis.com/css?family=Orbitron";
  @import "https://fonts.googleapis.com/css?family=Monoton";

  body,
  #app {
    height: 100%;
    background-color: black;
  }
</style>
```

### Container

The Container will set the background and position the content in the center of the page. For the background we'll pick one of the linear gradients on [www.gradientmagic.com](https://www.gradientmagic.com/).

```jsx
// src/Container.js

import React from "react"
import PropTypes from "prop-types"
import { View, Text } from "react-native"

export default function Container({ children }) {
  return (
    <View style={styles.container}>
      <View style={styles.content}>{children}</View>
    </View>
  )
}

const styles = {
  container: {
    backgroundColor: "black",
    backgroundImage:
      "repeating-linear-gradient(0deg, hsla(103,11%,32%,0.09) 0px, hsla(103,11%,32%,0.09) 1px,transparent 1px, transparent 11px),repeating-linear-gradient(90deg, hsla(103,11%,32%,0.09) 0px, hsla(103,11%,32%,0.09) 1px,transparent 1px, transparent 11px),linear-gradient(90deg, hsl(317,13%,6%),hsl(317,13%,6%))",

    height: "100%",
    minHeight: "100vh",
    padding: 24,
    justifyContent: "center",
    alignItems: "center",
  },
  content: {
    maxWidth: 785,
  },
}

Container.propTypes = {
  children: PropTypes.node,
}
```

Import the `Container` component and wrap the `Text` component in `src/App.js` to see the new background.

```jsx
// src/App.js
...
import Container from "./Container";
...
...
<Container>
  <Text>Hello World</Text>
</Container>
...
```

### Title

![title](https://www.consolelogs.dev/static/6a85f7ffc81cb29c8c26d1f3cbb269ad/c96f1/title.png)

The Title will render the page's title in the awesome [Monoton](https://fonts.google.com/specimen/Monoton) font. We can make this title stand out even more by adding a text shadow to create a glow effect.

```jsx
import React from "react"
import PropTypes from "prop-types"
import { View, Text } from "react-native"

export default function Title({ title }) {
  return <Text style={styles}>{title}</Text>
}

const styles = {
  fontSize: 70,
  fontFamily: "Monoton",
  color: "#FF00DE",
  letterSpacing: 8,
  textShadowColor: "#FF00DE",
  textShadowOffset: { width: -1, height: 1 },
  textShadowRadius: 30,
  marginBottom: 16,
  textAlign: "center",
}

Title.propTypes = {
  title: PropTypes.string,
}
```

Import `Title` component and replace the `Text` component in `src/App.js`.

```jsx
// src/App.js
...
<Container>
  <Title title={"How to Exit Vim"} />
</Container>
...
```

### Escape

The Escape component will display the information: "Hit Esc first", since you need to exit edit mode before running any of the commands to quit VIM. We're going to style this text in a similar way to the title, using text shadows to create a glow effect. But we're going to use the font [Orbitron](https://fonts.google.com/specimen/Orbitron) instead of Monoton, since it's more easily readable as text. Also, we need to distinguish between text that is describing what to do and text the visitor should type on their keyboard. We'll make this distinction with both font size and color. Description text will be `30px` and `#7fff00`, while command text will be `40px` and `#7DF9FF`.

```jsx
// src/Escape.js

import React from "react"
import { View, Text } from "react-native"

export default function Escape() {
  return (
    <View style={styles.container}>
      <Text style={styles.description}>
        Hit <Text style={styles.command}>Esc</Text> first
      </Text>
    </View>
  )
}

const styles = {
  container: {
    flexDirection: "row",
    justifyContent: "center",
    marginBottom: 24,
  },
  command: {
    fontSize: 40,
    color: "#7DF9FF",
    textShadowColor: "#7DF9FF",

    fontFamily: "Orbitron",

    textShadowOffset: { width: -2, height: 2 },
    textShadowRadius: 30,
  },
  description: {
    fontSize: 30,
    color: "#7fff00",
    textShadowColor: "#7fff00",
    fontFamily: "Orbitron",

    textShadowOffset: { width: -1, height: 1 },
    textShadowRadius: 30,
  },
}
```

Import the `Escape` component and add it below the `Title` in `src/App.js`.

```jsx
// src/App.js
...
<Container>
  <Title title={"How to Exit Vim"} />
  <Escape />
</Container>
...
```

### Command

The last component, Command, will display the keyboard command on the left and the description of what the command does on the right. The description will also have a `subDescription` that elaborates on what the command does. The text styles will match the styles we defined in the `Escape` component to keep the distinction between commands and descriptions.

```jsx
// src/Command.js

import React from "react"
import PropTypes from "prop-types"
import { View, Text } from "react-native"

export default function Command({ description, command, subDescription }) {
  return (
    <View style={styles.container}>
      <Text style={styles.command}>{command}</Text>
      <View style={styles.descriptionContainer}>
        <Text style={styles.description}>{description}</Text>
        {subDescription ? (
          <Text style={styles.subDescription}>({subDescription})</Text>
        ) : null}
      </View>
    </View>
  )
}

const styles = {
  container: {
    flexDirection: "row",
    justifyContent: "space-between",
    marginBottom: 30,
  },

  command: {
    fontSize: 40,
    color: "#7DF9FF",
    textShadowColor: "#7DF9FF",
    fontFamily: "Orbitron",
    textShadowOffset: { width: -2, height: 2 },
    textShadowRadius: 30,
    flex: 1,
    marginRight: 8,
  },
  descriptionContainer: {
    flex: 1,
  },
  description: {
    fontSize: 18,
    color: "#7fff00",
    textShadowColor: "#7fff00",
    fontFamily: "Orbitron",
    textShadowOffset: { width: -1, height: 1 },
    textShadowRadius: 30,
    textAlign: "right",
    marginBottom: 6,
  },
  subDescription: {
    fontSize: 12,
    color: "#59af03",
    textShadowColor: "#59af03",
    fontFamily: "Orbitron",
    textShadowOffset: { width: -1, height: 1 },
    textShadowRadius: 30,
    textAlign: "right",
  },
}

Command.propTypes = {
  description: PropTypes.string,
  command: PropTypes.string,
}
```

Import the `Command` component into `src/App.js` and add some commands to exit vim.

```jsx
// src/App.js
...
<Container>
  <Title title={"How to Exit Vim"} />
  <Escape />
  <View>
    <Command
      description={"Quit"}
      subDescription={"Fails if changes were made"}
      command={":q"}
    />
    <Command
      description={"Quit without writing"}
      subDescription={"Discard changes"}
      command={":q!"}
    />

    <Command
      description={"Write current file and Quit"}
      subDescription={"Saves changes even if there aren't any"}
      command={":wq"}
    />
    <Command
      description={"Write current file and Quit"}
      subDescription={"Saves changes only if there are changes"}
      command={":x"}
    />
    <Command
      description={"Quit without writing"}
      subDescription={"Discard changes"}
      command={"shift + ZQ"}
    />
    <Command
      description={"Write current file and Quit"}
      subDescription={"Saves changes only if there are changes"}
      command={"shift + ZZ"}
    />
  </View>
</Container>
...
```

We should now have a complete app displaying a few commands to exit VIM. The last step is to deploy it to Netlify.

![Cover](https://www.consolelogs.dev/static/924d9ce3ad10e5a2a9c248e204ba0547/799d3/cover.png)

## Deploying React Native Web to Netlify

[Netlify](https://www.netlify.com/) is a hosting provider that enables developers to host static websites. We can host our React Native Web app on Netlify by creating a static bundle of our app and assets using Webpack's production mode. Add the following as an `npm` script, named "build", to `package.json`.

```javascript
...
"scripts": {
    "build": "webpack --mode production",
    "start": "webpack-dev-server --config ./webpack.config.js --mode development",
},
...
```

Running this command in the terminal should output the app as static files `index.html` and `main.js`.

```shell
npm run build
```

Although we could upload these files directly to Netlify, it would be better to automate this process so that the project deploys when the `master` branch is updated on Github.

### Automated Builds on Netlify

Sign in or create a Netlify account, then go to Sites and click the "New site from Git" button.

![Netlify](https://www.consolelogs.dev/static/ba288ca3be155ce614850abee5d8f4cc/799d3/netlify.png)

Then click your Git provider and follow the instructions provided to connect it to Netlify.

![Git Provider](https://www.consolelogs.dev/static/0564d037ef059dcf9b252840056d6758/799d3/gitprovider.png)

Follow the onscreen prompts to choose the git repo where the app is stored. On the third step, choose the branch to deploy as "master". Fill in the build command as `npm run build`, and the publish directory as `dist`. Finally, click the "Deploy Site" button at the bottom.

![Build Settings](https://www.consolelogs.dev/static/b1728a25fb6a61932665a568d66db08b/799d3/build-settings.png)

The app should start deploying with a dash separated randomly generated name.

![Domain](https://www.consolelogs.dev/static/a62c72b5b21e88cfafa5c961f0a5c0b9/799d3/domain.png)

The app should now be live at that address within in the Netlify subdomain, for example [elegant-wescoff-754899.netlify.com](elegant-wescoff-754899.netlify.com).

## Conclusion

Building websites with React Native Web is extremely similar to building websites with raw React. The only significant difference in this project compared to an identical project using raw React is that all the `div` and `p` tags were replaced with `View` and `Text` components. This is small price to pay for having the option of supporting more platforms in the future without a significant rewrite, even if your project does not support multiple platforms. Having said that, the example above is extremely simple, more complex applications might need to pay a higher price with limitations or components that are difficult to write with React Native. But even with that higher price, the value of sharing code across so many platforms is, in my opinion, worth it.