Deploying a React app alongside a server to Heroku (and S3)

Anthony Accomazzo

March 29, 2017 // 10 min read

We've previously covered how to deploy a React app to S3. But what about deploying a React app that's backed by an API server, like the one we discussed in this post?

While the README for that project discusses a solution, it doesn't go into much depth. In this post, we'll get #deep.

We'll:

  1. Discuss the deployment strategy with Heroku
  2. Deploy the app to Heroku
  3. Discuss an alternative setup that involves S3 and Heroku together

If you want to follow along, go ahead and clone the example app now:

$ git clone [email protected]:fullstackreact/food-lookup-demo
# Install npm packages for client and server
$ cd food-lookup-demo
$ npm i && cd client && npm i && cd ..

Let's get started.

Our deployment strategy

What's Heroku?

Heroku is a Platform-as-a-Service (PaaS). They make deploying dynamic web apps dead simple, taking care of lots of the underlying details. What's more, they have an ecosystem of add-ons that you can easily plug into your app. Should your app need databases, caching, or logging services, Heroku has a platform partner that you can integrate with.

Heroku has a free tier. Heroku calls each virtual server (or "compute instance") a "dyno." The free tier gives you one dyno. The free tier is not sufficient for a production app but is fine for our hobbyist purposes.

Note that the free dyno "sleeps" after some period of inactivity. This means that initial requests to "wake up" the dyno will take significantly longer as your dyno needs to be retrieved and booted.

You deploy to your dyno using git push. After pushing your code via git up to your dyno, your app will restart and your changes will be live.

The strategy

The food lookup app involves a React app that interfaces with a Node+Express API server. As you might recall from our original post about this app, we run two servers in development: a Webpack server that serves our React app and the Node+Express app that serves our API. The browser communicates exclusively with the Webpack server which then proxies API-specific requests to our API:

Flow diagram of the two servers in development
Request flow in development

This works fine for development. But it's not a valid strategy for deployment:

  1. The Webpack development server is intended for, well, development.
  2. We can only run one server on each dyno.

For #1, recall that the Webpack development server is watching changes to your React app/assets and is compiling everything on the fly. This is purely a development feature -- we don't want to use it in production. As we discussed briefly in the last post, we can have Webpack produce an optimized bundle for our app that's intended for production use.

For #2, even if we wanted to use a Webpack development server to toy around with our app on Heroku, we'd need to use two dynos to do so -- one dyno for our Webpack dev server and one for our API server. Getting the two to communicate would be a little tricky and we'd no longer be eligible for the free tier. 💸

Instead, we want to use the build task provided by create-react-app to produce a static bundle of our React app. This static bundle will contain optimizations for production.

The neat part is that we can then put this bundle really anywhere. It will be a completely self-contained folder. All of our app's JavaScript, HTML, and CSS is included, along with any images. Because these assets are static, any basic web host will do.

Our Node+Express app, however, will be its own running process. So it will need a more specialized environment to run in. That's where a PaaS like Heroku comes in.

So, one configuration for deployment is to produce our static bundle and stick it on some basic file server on the web. We can use Amazon's S3, for example. We can then deploy our API server to Heroku. The two would communicate like this:

user's browser talks to React app on S3 which talks to API server on Heroku
Possible request flow in production

Again, any file host for our React app's static bundle would do. The user's browser would download and run our React app, which would then make requests to our API server.

There are a couple hurdles with this approach. One hurdle is that we'd need to have two deployment targets, both S3 (for the React app) and Heroku (for the API server).

We'll discuss this deployment strategy at the end of this post.

For now, to keep things simple, it would be great if we could keep to a single deployment target, Heroku. At the time of writing, Heroku doesn't have a canonical way to easily deploy static sites. But instead, we can have our API server also serve the static assets for our React app. Here's what that request flow would look like:

  1. Browser requests /
  2. Node+Express server serves the JS, HTML, and CSS for our React app
  3. React app loads in the browser
  4. React app communicates with /api/food on our API server

A diagram of this approach:

user's browser talks to API server which serves both the React app and the API endpoint for food data

So our API server becomes an all-purpose server. It serves both the static assets for our site as well as the API endpoint that our React app communicates with.

We'll see how we accomplish this in a bit. First, let's setup Heroku.

Setting up Heroku

To setup Heroku, we need to:

  1. Sign up for an account
  2. Install Heroku's command-line tool
  3. Instantiate the Heroku app

1. Sign up

Sign up here.

2. Install the CLI tool

Follow this tutorial.

3. Instantiate the Heroku app

If you haven't already, clone the repository we're working with in this post:

$ git clone [email protected]:fullstackreact/food-lookup-demo
$ cd food-lookup-demo
# Install npm packages for client and server
$ npm i && cd client && npm i && cd ..

Inside that folder, we can run heroku apps:create to instantiate a new Heroku app. This does two primary things:

  1. Sets up our app on Heroku.
  2. Adds the heroku target to git. We'll see what this means in a moment.

The command takes this format:

$ heroku apps:create <YOUR-APP-NAME>

The name of your app must be unique across Heroku. That's because Heroku will host your app at a subdomain:

http://<YOUR-APP-NAME>.herokuapp.com

Run that command now.

The name food-lookup-demo is already taken. I wonder who has it? 🙃

Our Heroku app and tooling is all set up. And our app is instantiated on Heroku.

To deploy our app, we'll eventually run this command:

$ git push heroku master

In the past, you've probably run this command:

$ git push origin master

If you're using a service like GitHub, origin points to GitHub. Here, heroku points to our Heroku app. We're able to use git to upload our app to Heroku. Neat.

But first, we need to prepare our app for deployment.

Preparing the app

Before deploying our app, we need to create our React app's static bundle. create-react-app provides the handy build command. Change into the client directory and run it:

$ cd client
$ npm run build

The build/ folder will contain all the necessary files to run our React app.

Now, in order for build/ to make it up to Heroku, we have to add it to git. Do so now:

# from the root of the project
$ git add client/build
$ git commit -m 'Adding `build` to source control'

Next, as we discussed earlier, we need to have our Node+Express server serve this static bundle. If you check out server.js, you'll see that we already added a handler for this:

// Express only serves static assets in production
if (process.env.NODE_ENV === "production") {
  app.use(express.static("client/build"));
}

In production, our Node+Express app will serve /build. /build contains an index.html. When the user's browser requests /, our Node+Express app will serve index.html which will load our React app.

So, our app is ready for deployment.

Deploy

With everything in place, deployments are a cinch:

$ git push heroku master

After our repository makes it to Heroku, we'll start seeing a message log of the deploy process:

Output of deploy process to Heroku

After the deploy completes, Heroku will spit out the URL of the app. We can also use this command to open the page in our browser:

$ heroku open

Run the command and witness the might of your live, deployed app!

Deploying to both S3 and Heroku

As we mentioned earlier, another deployment strategy is to deploy your React app's bundle to a static web host like S3 and deploy your API server to Heroku:

user's browser talks to React app on S3 which talks to API server on Heroku

This strategy is a bit more work, but has a few nice features:

  1. You can keep your client app and API server entirely separate
  2. You decouple the deployments of your front-end app and your back-end API

For prototyping and hobby projects, neither of these features are particularly compelling. But for larger teams or larger apps, there are real benefits to this approach.

We recently wrote a post describing deploying a React app to S3. You can use the concepts from that post to deploy your React app to S3 and then the concepts from this post to deploy your API server to Heroku.

However, there is one caveat: CORS. We discuss CORS (or Cross-Origin Resource Sharing) in this post. The gist: The user's browser will have loaded your React app from S3. Your React app will then be attempting to make requests out to your API server over on Heroku. For security reasons, your browser will prevent this from happening.

There are several ways to solve this. One relatively straightforward solution: We can add some headers to our API server's response that tell the browser that it's OK receiving requests from the domain of our Heroku app. We could add lines like this to server.js:

app.use((req, res, next) => {
  res.header(
    "Access-Control-Allow-Origin",
    "http://<YOUR-APP-NAME>.herokuapp.com"
  );
  res.header(
    "Access-Control-Allow-Headers",
    "Origin, X-Requested-With, Content-Type, Accept"
  );
  next();
});

That adds two headers to every outgoing response, telling the browser our API server is OK with requests originating from our React app. You can also use the express library cors to set this up.

With those headers in place, everything should work swimmingly.

You're now armed with two strategies for sharing your projects with the world. Questions, comments, or other ideas? Leave a comment below.


Because you found this post helpful, you'll love our book — it's packed with over 800 pages of content and over a dozen projects, including chapters on React fundamentals, Redux, Relay, GraphQL, and more.

Learn React the right way

The up-to-date, in-depth, complete guide to React and friends.

Download the first chapter

Anthony Accomazzo

Passionate about teaching, Anthony has coached many beginning programmers through their early stages. Before Fullstack React, he led the development of IFTTT's API platform. A minimalist and a traveler, Anthony has been living out of a backpack for the past year.