Deploy create-react-app with react-router on Heroku
Tags: webdev, beginners, react, heroku
Published: 2023-10-14 15:28:41

Most beginner personal web development projects start with using create-react-app. create-react-app is a simple react-based application, prominently used by newcomers as a tutorial. However, it does not lower its quality as a production-ready framework. Coupled with other packages and plugins found in npm registry, we can create a professional website in no time.

Using create-react-app with react-router allows creating routes for our React.js application. This blog will focus on SSR with code bundling to improve performance. Furthermore, we will deploy to Heroku using the production build from `npm run build`, which acts as a static site rather than dynamic site (`npm start`). 

---

## tl;dr

1. Read this [example](https://legacy.reactjs.org/docs/code-splitting.html#route-based-code-splitting) on how to enable Server Side Rendering through React Router
2. Install serve (`npm i serve`) for serving built static web
3. Update the Heroku Procfile to `web: serve -s build`

---

## 1. create-react-app with react-router

react-router enables create-react-app application to use routing, primarily Client Side Routing (CSR). However, in this post, we are only going to use react-router for its Server Side Routing (SSR).

### A little note about CSR vs SSR

In shorthand, in Server Side Routing (SSR), the user requests a whole new page, while in Client Side Routing (CSR), the user only request changes in data and the page changes accordingly. So, SSR requires the user to download more data (and thus slower) compared to CSR.

react-router focuses on CSR, but we can use it as SSR as well. You can find the tutorial for this in [their website](https://reactrouter.com/en/main/start/tutorial) (SSR up until Nested Routes section).

### SSR through Code Bundling

CSR might have slower **initial** load since the user needs to download all data, even the ones that they never going to see. Let's say there are 3 routes: `/home`, `/about`, and `/user`. If we use react-router as it is from the tutorial, then the user will download all content in those three pages, although they only open one of the page. Code bundling will solve this issue. Refer [this legacy docs](https://reactjs.org/docs/code-splitting.html#route-based-code-splitting) for how to implement code bundling.

---

## 2. How Heroku Runs create-react-app

This is how Heroku runs create-react-app on default (applies to with or without react-router).

1. Heroku automatically detects that the app is a Node.js app
2. Heroku runs `npm install`, followed by `npm run build` if it is defined in package.json. create-react-app already defined `npm run build`.
3. Heroku looks for Procfile file and all defined processes. Usually this file only contains a single line: `web: npm start`.
4. Heroku will run their dynos (server) based on one of these processes. 

However, this is not optimized since we are using the development build. Heroku **did** build the production build, but they still use the development build (note the `web: npm start` command in Procfile).

### Run the Production Build (Static Site) _Locally_

Using `npm start`, the website will be run as a dynamic site. If your website has no dynamic or back-end code, then we can build it to be a static website.

First, we need to build the app first by running `npm run build`. It will create a new directory called `build` which contains static HTML, CSS, JavaScript, public files, etc. We can not just open the `index.html` file and expect it to work. We need to run a static server.

> There are many different ways to run a static server. In Node.js, you can install packages like [`http-server`](https://www.npmjs.com/package/http-server), [`static-server`](https://www.npmjs.com/package/static-server), and [`serve`](https://www.npmjs.com/package/serve). In Python, you can use [`http.server`](https://developer.mozilla.org/en-US/docs/Learn/Common_questions/Tools_and_setup/set_up_a_local_testing_server#using_python).

We are going to use [serve](https://www.npmjs.com/package/serve) as it is fast and simple to use.

```
npm install serve
serve -s build
```

---

## 3. Run the Production Build (Static Site) on _Heroku_

Heroku automatically runs `npm run build`, but it still run the application using `npm start`, which is not optimized. We can use `serve` to run the production build (static site).

Add this dependency in package.json (or just run `npm install serve` if you're not sure about the version).

```
"serve": "^14.2.0",
```

Update the Procfile.

```
web: serve -s build
```

---

## That's It

And those are the reasons behind why I did those steps to run `create-react-app` with `react-router` on Heroku.

### Alternative without `serve` (Using Express)

Instead of installing `serve` or other static site server, we can create a server.js file in the root directory.

```js
const express = require('express');
const path = require('path');
const app = express();

app.use(express.static(path.join(__dirname, 'build')));

app.get('/', function (req, res) { // marked
    res.sendFile(path.join(__dirname, 'build', 'index.html'));
});

app.listen(9000);
```

If you use react-router, then change the marked line to `app.get('/*', function(req, res)) {`.

Also don't forget to update the Procfile.

```
web: node server.js
```

And that's it! Now Heroku will run the production build without installing `serve` or any other application.