Build and Deploy a React Native Web App on Netlify from Scratch
December 18, 2019
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 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 from scratch without any template.
GithubSetup and Configuration
We're going to setup everything from scratch with the minimum configuration required.
Create a new directory, howtoexitvim
.
1.mkdir howtoexitvim
Initialize a package.json
and change the main entry point to src/index.js`
.
1.npm init
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:
1.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 by Mark A, to learn how each of these sections work.
1.const HtmlWebPackPlugin = require("html-webpack-plugin");2.3.module.exports = {4. module: {5. rules: [6. {7. test: /.(js|jsx)$/,8. exclude: /node_modules/(?!()/).*/,9. use: {10. loader: "babel-loader",11. options: {12. presets: ["@babel/preset-env", "@babel/preset-react"],13. },14. },15. },16. {17. test: /.html$/,18. use: [19. {20. loader: "html-loader",21. },22. ],23. },24. ],25. },26. plugins: [27. new HtmlWebPackPlugin({28. template: "./public/index.html",29. filename: "./index.html",30. }),31. ],32.33. devServer: {34. historyApiFallback: true,35. contentBase: "./",36. hot: true,37. },38.};
Let's also alias react-native
to react-native-web
so that when Webpack sees this:
1.import { SomeComponent } from 'react-native'
It will instead import the component from react-native-web
, like this:
1.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
.
1....2.resolve: {3. alias: {4. "react-native": "react-native-web"5. },6. extensions: [".web.js", ".js"]7. },8....
Finally, create an npm
script to run the webpack-dev-server
.
1....2."scripts": {3. "start": "webpack-dev-server --config ./webpack.config.js --mode development",4. },5....
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.
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.
1.<!DOCTYPE html>2.<html>3. <head>4. <title>How To Exit Vim</title>5. </head>6. <body>7. <div id="app"></div>8. </body>9.</html>
Next, create src/App.js
with the text "Hello World" using React Native's <Text>
component.
1.import React from "react";2.import ReactDOM from "react-dom";3.import { Text } from "react-native";4.5.export default class App extends React.Component {6. render() {7. return <Text>Hello World</Text>;8. }9.}
The last file we'll need is src/index.js
, which will render the app in the DOM using react-dom
.
1.import React from "react";2.import ReactDOM from "react-dom";3.4.import App from "./App";5.6.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".
Building howtoexitvim.org
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
:
1.<style>2.@import "https://fonts.googleapis.com/css?family=Orbitron";3.@import "https://fonts.googleapis.com/css?family=Monoton";4.5.body,6.#app {7. height: 100%;8. background-color: black;9.}10.</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.
1.// src/Container.js2.3.import React from "react";4.import PropTypes from "prop-types";5.import { View, Text } from "react-native";6.7.export default function Container({ children }) {8. return (9. <View style={styles.container}>10. <View style={styles.content}>{children}</View>11. </View>12. );13.}14.15.const styles = {16. container: {17. backgroundColor: "black",18. backgroundImage:19. "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%))",20.21. height: "100%",22. minHeight: "100vh",23. padding: 24,24. justifyContent: "center",25. alignItems: "center",26. },27. content: {28. maxWidth: 785,29. },30.};31.32.Container.propTypes = {33. children: PropTypes.node,34.};
Import the Container
component and wrap the Text
component in src/App.js
to see the new background.
1.// src/App.js2....3.import Container from "./Container";4....5....6.<Container>7. <Text>Hello World</Text>8.</Container>9....
Title
The Title will render the page's title in the awesome Monoton font. We can make this title stand out even more by adding a text shadow to create a glow effect.
1.import React from "react";2.import PropTypes from "prop-types";3.import { View, Text } from "react-native";4.5.export default function Title({ title }) {6. return <Text style={styles}>{title}</Text>;7.}8.9.const styles = {10. fontSize: 70,11. fontFamily: "Monoton",12. color: "#FF00DE",13. letterSpacing: 8,14. textShadowColor: "#FF00DE",15. textShadowOffset: { width: -1, height: 1 },16. textShadowRadius: 30,17. marginBottom: 16,18. textAlign: "center",19.};20.21.Title.propTypes = {22. title: PropTypes.string,23.};
Import Title
component and replace the Text
component in src/App.js
.
1.// src/App.js2....3.<Container>4. <Title title={"How to Exit Vim"} />5.</Container>6....
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 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
.
1.// src/Escape.js2.3.import React from "react";4.import { View, Text } from "react-native";5.6.export default function Escape() {7. return (8. <View style={styles.container}>9. <Text style={styles.description}>10. Hit <Text style={styles.command}>Esc</Text> first11. </Text>12. </View>13. );14.}15.16.const styles = {17. container: {18. flexDirection: "row",19. justifyContent: "center",20. marginBottom: 24,21. },22. command: {23. fontSize: 40,24. color: "#7DF9FF",25. textShadowColor: "#7DF9FF",26.27. fontFamily: "Orbitron",28.29. textShadowOffset: { width: -2, height: 2 },30. textShadowRadius: 30,31. },32. description: {33. fontSize: 30,34. color: "#7fff00",35. textShadowColor: "#7fff00",36. fontFamily: "Orbitron",37.38. textShadowOffset: { width: -1, height: 1 },39. textShadowRadius: 30,40. },41.};
Import the Escape
component and add it below the Title
in src/App.js
.
1.// src/App.js2....3.<Container>4. <Title title={"How to Exit Vim"} />5. <Escape />6.</Container>7....8.
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.
1.// src/Command.js2.3.import React from "react";4.import PropTypes from "prop-types";5.import { View, Text } from "react-native";6.7.export default function Command({ description, command, subDescription }) {8. return (9. <View style={styles.container}>10. <Text style={styles.command}>{command}</Text>11. <View style={styles.descriptionContainer}>12. <Text style={styles.description}>{description}</Text>13. {subDescription ? (14. <Text style={styles.subDescription}>({subDescription})</Text>15. ) : null}16. </View>17. </View>18. );19.}20.21.const styles = {22. container: {23. flexDirection: "row",24. justifyContent: "space-between",25. marginBottom: 30,26. },27.28. command: {29. fontSize: 40,30. color: "#7DF9FF",31. textShadowColor: "#7DF9FF",32. fontFamily: "Orbitron",33. textShadowOffset: { width: -2, height: 2 },34. textShadowRadius: 30,35. flex: 1,36. marginRight: 8,37. },38. descriptionContainer: {39. flex: 1,40. },41. description: {42. fontSize: 18,43. color: "#7fff00",44. textShadowColor: "#7fff00",45. fontFamily: "Orbitron",46. textShadowOffset: { width: -1, height: 1 },47. textShadowRadius: 30,48. textAlign: "right",49. marginBottom: 6,50. },51. subDescription: {52. fontSize: 12,53. color: "#59af03",54. textShadowColor: "#59af03",55. fontFamily: "Orbitron",56. textShadowOffset: { width: -1, height: 1 },57. textShadowRadius: 30,58. textAlign: "right",59. },60.};61.62.Command.propTypes = {63. description: PropTypes.string,64. command: PropTypes.string,65.};66.
Import the Command
component into src/App.js
and add some commands to exit vim.
1.// src/App.js2....3.<Container>4. <Title title={"How to Exit Vim"} />5. <Escape />6. <View>7. <Command8. description={"Quit"}9. subDescription={"Fails if changes were made"}10. command={":q"}11. />12. <Command13. description={"Quit without writing"}14. subDescription={"Discard changes"}15. command={":q!"}16. />17.18. <Command19. description={"Write current file and Quit"}20. subDescription={"Saves changes even if there aren't any"}21. command={":wq"}22. />23. <Command24. description={"Write current file and Quit"}25. subDescription={"Saves changes only if there are changes"}26. command={":x"}27. />28. <Command29. description={"Quit without writing"}30. subDescription={"Discard changes"}31. command={"shift + ZQ"}32. />33. <Command34. description={"Write current file and Quit"}35. subDescription={"Saves changes only if there are changes"}36. command={"shift + ZZ"}37. />38. </View>39.</Container>40....41.
We should now have a complete app displaying a few commands to exit VIM. The last step is to deploy it to Netlify.
Deploying React Native Web to Netlify
Netlify 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
.
1....2."scripts": {3. "build": "webpack --mode production",4. "start": "webpack-dev-server --config ./webpack.config.js --mode development",5.},6....
Running this command in the terminal should output the app as static files index.html
and main.js
.
1.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.
Then click your Git provider and follow the instructions provided to connect it to Netlify.
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.
The app should start deploying with a dash separated randomly generated name.
The app should now be live at that address within in the Netlify subdomain, for example 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.