React App From Scratch and Deploy to Heroku
Tags: react, heroku, webdev, tutorial
Published: 2022-01-21 12:23:54

Tired of using CRA (Create React App) for everything? This post is for you. In this post, we'll set up what could be used as the base for you to create your own React project template, already ready for deploying to Heroku.

**Note:** If you try to code along with this post, be aware of the packages versions I'm using, though. For reference, the final `package.json` is listed right after the `Conclusion` section of this post.

Create your project folder, run `npm init --y` to create your `package.json`, and let's start! 

## React Installation and Package Structure

We'll start installing what we need about react with `npm install react react-dom`.

Now create two folders: 

- `build`: Where your built app will be placed;
- `src`: Where your actual app components will be placed;
- `public`: Where we'll put our static files.

Populate your `src` folder with two files:

- `App.js`, the main component of your application:

```javascript
import React from "react";

function App() {
  return <div>Hello World!</div>
}

export default App;
```

- `index.js`, the entry point of your application:

```javascript
import React from "react";
import ReactDom from "react-dom";
import App from "./App";

ReactDom.render(
  <App />,
  document.getElementById("root")
);
```
Did you notice we need an html element with id `root` to make this work? Don't worry about it, it's our next step.

## Webpack and Project Up!

Webpack is our project bundler, the one responsible for getting our javascript files and making them usable in the browser. Install the necessary webpack packages with `npm install webpack webpack-cli --save-dev`.

In your project root folder, create the `webpack.config.js`. This is the basic structure we'll fill:

```javascript
module.exports = {
  mode: "development",
  entry: "",
  output: {
  },
  plugins: [],
  module: {},
}
```

- `mode`: If you're running in production or development. You can use an ENV variable to determine the environment if you want (e.g., `process.env.MY_ENV`);
- `entry`: The entry point of our application, or in other words, from where the bundler must start looking in order to bundle our application;
- `output`: Output interface to configure where the bundled files should be placed;
- `plugins`: Plugins interface in case we wanna use any plugin;
- `module`: Interface to configure Webpack dependencies if you have any.

Let's start with the `module`. You probably know about [Babel](https://babeljs.io/) to get browser compatible-javascript, and I wanna make it available through Webpack `module`. Also, you probably will need some Babel presets (a.k.a., Babel plugins to support particular language features), so install everything with `npm install @babel/core babel-loader @babel/preset-env @babel/preset-react --save-dev`, and update your webpack `module` to be like this:

```javascript
module:{
    rules:[
      {
        test:/\.js$/,
        exclude:/node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env','@babel/preset-react']
          }
        }
      },
    ]
},
```

I'll not elaborate about these presets and how `rules` are configurated, you can search more about it if you're interested, and keep in mind those configurations aren't something you need to memorize because you'll usually find them in the [npm package description](https://www.npmjs.com/package/babel-loader).

Going next for the `entry`, we simply set it to our application entry point:

```javascript
...

entry: './src/index.js',

...
```

For the output we want to have our bundled files to be placed in a single file named `bundle.js` inside our `build` folder: 

```javascript
const path = require('path'); // add this at the top of your webpack.config.js
...

  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'build'),
  },

...  
```

This bundle file will be loaded through a `<script>` tag in your `index.html` file. But here is the question: how do we generate this bundle file? First off, let's create our `index.html` inside our `public` folder:

```html
<html>
   <head>
      <title>React App From Scratch</title>
   </head>
   <body>
      <div id="root"></div>
   </body>
</html>
```
`Note:` it's just a simple html you can update the way you want, just don't forget to keep `<div id="root"></div>` because is where we specified that the `App` component will be rendered (back to our `index.js`).

Now we can add the package `npm install html-webpack-plugin --save-dev` that will handle the `bundle.js` generation. Update the `plugins` with this:

```javascript
...

const HtmlWebPackPlugin = require("html-webpack-plugin");

...

plugins:[
  new HtmlWebPackPlugin({
    template: path.resolve( __dirname, 'public/index.html' ),
    filename: 'index.html'
  })
],

...
```

When we build our application, a default `index.html` will be generated if we use `HtmlWebPackPlugin` `template` and `filename`. We don't wanna have the default one, though, so we're saying to template it with our `public/index.html` and name it with this same filename. 

Before we can run our app, add the server boot script inside `scripts` of your `package.json`: `"dev": "webpack-dev-server"`. We also need to add in our `webpack.config.js` the option indicating we're using a dev server ():

```javascript
...

devServer: {},

...
```

For reference, here is the final version of our `webpack.config.js` and `package.json`:

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

module.exports = {
  mode: "development",
  entry: './src/index.js',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'build'),
  },  
  plugins:[
    new HtmlWebPackPlugin({
      template: path.resolve( __dirname, 'public/index.html' ),
      filename: 'index.html'
    })
  ],
  module:{
      rules:[
        {
          test:/\.js$/,
          exclude:/node_modules/,
          use: {
            loader: 'babel-loader',
            options: {
              presets: ['@babel/preset-env','@babel/preset-react']
            }
          }
        },
      ]
  },
  devServer: {},
}
```

```json
// package.json
{
  "name": "react-app-from-scratch",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "dev": "webpack-dev-server"
  },
  "author": "",
  "license": "MIT",
  "dependencies": {
    "react": "^17.0.2",
    "react-dom": "^17.0.2"
  },
  "devDependencies": {
    "@babel/core": "^7.16.7",
    "@babel/preset-env": "^7.16.7",
    "@babel/preset-react": "^7.16.7",
    "babel-loader": "^8.2.3",
    "html-webpack-plugin": "^5.5.0",
    "webpack": "^5.65.0",
    "webpack-cli": "^4.9.1",
    "webpack-dev-server": "^4.7.2"
  }
}
```

Just run `npm run dev` and your server is up! We didn't change the default port of the server, so you'll be able to see our "Hello World!" message accessing `http://localhost:8080`.


![Hello World page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gkan7mqglyq04rfphfas.png)

## Deploy to Heroku

To deploy to Heroku we need beforehand:

-  A web server: we're essentially serving static files, and Heroku requires a web server. We can create our own webserver to simply serve the static files;
- `npm run build` `npm run start` command: those commands are the default ones Heroku will run to build and start our application.

Run `npm install express` that we'll use to create our node server. In your root repository create your `server.js`:

```javascript
const path = require('path');
const express = require('express');

const app = express();

app.use(express.static(path.join(__dirname, 'build')));
app.set('port', process.env.PORT || 3000);

const server = app.listen(app.get('port'), function() {
  console.log('listening on port ', server.address().port);
});
```

This is the most basic node server snippet you'll find over the internet, and the magic happens here `app.use(express.static(path.join(__dirname, 'build')))`: we're saying that our built app is inside the `build` folder, and those files should be served as static.

Now we finish adding the `build` and `start` scripts in our `package.json`:

```json
{
  "name": "react-app-from-scratch",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "dev": "webpack-dev-server",
    "start": "node server.js",
    "build": "webpack --config ./webpack.config.js --mode='production'"
  },
  "author": "",
  "license": "MIT",
  "dependencies": {
    "express": "^4.17.2",
    "react": "^17.0.2",
    "react-dom": "^17.0.2"
  },
  "devDependencies": {
    "@babel/core": "^7.16.7",
    "@babel/preset-env": "^7.16.7",
    "@babel/preset-react": "^7.16.7",
    "babel-loader": "^8.2.3",
    "html-webpack-plugin": "^5.5.0",
    "webpack": "^5.65.0",
    "webpack-cli": "^4.9.1",
    "webpack-dev-server": "^4.7.2"
  }
}
```

You can test if everything is working properly running your newly added scripts: `npm run build` and `npm run start`. You should be able to see our "Hello, World!" message again, now on `http://localhost:3000`.

And this is it, you are ready to deploy to Heroku!

**Note:** Just ensure yourself that `heroku/nodejs` buildpack is present in your Heroku app. Probably is, because already comes by default when you create your app!

## Conclusion

This post shows how you can create your React app by scratch. It was explained the basics of the webpack configuration file, and how you can create your webserver to deploy your application to Heroku. 

Bear in mind that those configurations don't deliver you a template for a React app ready for production, there's a lot more to be done before you can say this, like server routing, code splitting, etc. But with what it's presented here you have a start point in order to start building your own template, in case you just don't wanna use something like CRA.

***

This is it! If you have any comments or suggestions, don't hold back, let me know.

***

Options if you like my content and would like to support me directly (never required, but much appreciated):

BTC address: `bc1q5l93xue3hxrrwdjxcqyjhaxfw6vz0ycdw2sg06`

[![buy me a coffee](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z1n0hai66hv33dphoiic.png)](https://www.buymeacoffee.com/rwehresmann)