Deploying a Rails + React.js App to Heroku
By: Alejandro Sabogal
Tags: Ruby on Rails, React, Deployment, Heroku, Reactjs

Heroku Platform

So you have a finished application that runs on your localhost machine. It’s time now to make your application public and available to users on the web; in essence, to move out of development and into production stage. This isn’t a straight forward task and usually requires some careful tweaking to make sure everything runs smoothly on the web.

For this article, in an attempt to save you some headaches and countless trial and error commits and erroneous deploys, I will go in-depth on deploying a Rails with React.js application using Heroku.

Before starting, I’m assuming you’ve done a few things:

You have a working Rails application with React.js as the client on the same stack that can run on your local environment You used the rails new –api command to build the app in API mode (best practice in this case) You used create-react-app to build the React client Your Ruby version is 2.4.0 or above Your Rails version is 5.x You know what Heroku is and have set up an account and have their CLI installed You have Yarn or NPM installed (we’ll use Yarn in this article)

Before you can even attempt deploying the app, you must first make some changes to the default configurations in Rails and the overall project in general.

I suggest you create a separate git branch for these coming changes in case you want to keep your original development version. Once your deployment/production version is complete, you can merge to your master branch.

Ok, let’s get to it!

Changing the Rails Database

By default, Rails uses SQLite3 for its database, however, SQLite is not intended as a production-grade database. Heroku works with PostgreSQL, a more robust database. If you’re not familiar with PostgreSQL, I encourage you to research it; it’s really powerful and used by many applications.

To move from sqlite3 to postgres , open your Gemfile and remove or comment out the sqlite3 gem and add the postgres gem:

# gem ‘sqlite3’

gem ‘pg’

Alternatively, you can group your gems on their respective environments if you want to preserve your current database on the development environment:

group :development do

gem ‘sqlite3’

end group :production do

gem ‘pg’

end

At this point, if you don’t have one, I suggest you create a seed file based on your current database. This will allow you to move or recreate your current data into the new PostgreSQL database. If you plan on having a blank production database, then this won’t be necessary.

Once your gemfile has been updated, re-install the dependencies by running bundle install in your terminal.

By adding the postgres gem, we’ve told Rails to start using PostgreSQL as the database. For this to work, you need to change the database adapter from sqlite3 to postgresql in the database configuration file config/database.yml :

development:

adapter: postgresql

database: my_database_development

pool: 5

timeout: 5000

test:

adapter: postgresql

database: my_database_test

pool: 5

timeout: 5000 production:

adapter: postgresql

database: my_database_production

pool: 5

timeout: 5000

Note: This setup makes all environments use PostgreSQL. If you want to keep using SQLite on development, leave the development adapter pointing to sqlite3. Also, make sure you substitute “my_database” to your app’s root directory name.

At this point, you should commit your changes to your production branch.

ActiveStorage Changes

Rails’ ActiveStorage allows you to attach files to ActiveRecord models. If you are using ActiveStorage to attach pictures, audio, or other files, you need to make some configuration changes before deployment. This is because Heroku uses an “ephemeral” disk that allows you to write files to it, however, those files won’t persist after the Heroku app is restarted.

Instead of storing uploaded files directly on Heroku, it is recommended to use a cloud file storage service such as AWS or Google Cloud. In this article, we’ll use Google Cloud Storage as it’s very straight forward and it’s free to use for the first year.

Go to https://cloud.google.com and sign in with your Google account or create one. You’ll be taken to the Google Cloud Platform where you will create a new project. Name the project as your application’s name:

Next, open the Google Cloud Platform menu on the left side. Under “Storage”, hover over “Storage” and click on “Browser”. Click on the “Create Bucket” button to create a new “bucket” for your project:

A bucket is where your data is held on the Google Cloud. The name must be globally unique across Cloud Storage. For the settings, select Regional storage class, choose your location, and create:

Your bucket is now created. Now you have to give it access to your Rails backend. To do so you need to get an authorization key. On the main menu, under API’s & Services”, click on “Credentials”. This will open up a new page to create new credentials; create a new Service account key:

Pick a name for your service account, select Project Owner as the Role, and make sure the Key Type is in JSON format:

After clicking “Create”, a public/private key pair is generated and downloaded to your computer. Make sure to keep this in a safe place as it is the only copy of it you will have.

You now have a working Google Cloud project to upload and host your application’s files. Now let’s set up the Rails backend to work with it:

You need to let your app know about the authorization keys you just saved. First create a secrets folder inside your app’s config folder.

Add config/secrets/* to the app’s .gitignore file. This will prevent the contents of the secrets folder to be included in git version control, and uploaded and shared to git remotes.

Now rename the downloaded .json file to: your-app-name.json and move it into the config/secretes folder.

You now need to configure Rails to use the Google Cloud Service. First, add the Google Storage gem to your gemfile:

gem "google-cloud-storage", "~> 1.8", require: false

And run bundle install in the terminal.

Next, change the app’s storage configuration file config/storage.yml . Make the following changes:

google:

service: GCS

project: your-app-name

credentials: <%= ENV['GOOGLE_APPLICATION_CREDENTIALS'] %>

bucket: your-app-name

Note: Make sure you add your corresponding project and bucket names instead of “your-app-name”.

Now, tell Rails to use :google for the production environment. In config/environments/production.rb , make the following change:

config.active_storage.service = :google

Note: Your development environment will continue to use ActiveStorage. If you want to use Google Cloud for development environment as well, you need to configure Rails to do so:

google_dev:

service: GCS

project: your-app-name

credentials: <%= Rails.root.join("config/secrets/ your-app-name.json ") %>

bucket: your-app-name

config/environments/development.rb :

config.active_storage.service = :google_dev

At this point, if this was a single Rails application, you could create the database and do your migrations, and then deploy it to Heroku. But since we have a React.js client, we need to change a few more things on the backend. This would be a good time to commit your changes.

Building the Production Client and Setting Up Rails for Deployment

Just as with a single React.js application, the client needs to be built to create a build directory that will hold the app’s production static files. Included in these files will be index.html , which will be served to visitors of the site. These files will be contained on the public folder of the Rails backend, which is what we want to deploy to Heroku. We can use Yarn (or NPM) to create the build and deploy scripts that will prepare our app for production.

Heroku works very good with Node.js applications and will recognize such apps if it sees a package.json file in the root of the application. Let’s create that file:

In your terminal and in the project’s root directory, execute yarn init and follow instructions.

This will add the package.json file to the Rails backend that Heroku will then use to build and deploy the app. It will look something like this:

{

"name": "my-awesome-package",

"version": "1.0.0",

"description": "The best package you will ever find.",

"main": "index.js",

"repository": {

"url": "https://github.com/yarnpkg/example-yarn-package",

"type": "git"

},

"author": "Yarn Contributor",

"license": "MIT"

}

We now need to add the scripts that will be executed for such actions:

"scripts": {

"build": "cd client && yarn install && yarn build && cd ..",

"deploy": "cp -a client/build/. public/",

"postinstall": "yarn build && yarn deploy && echo 'Client built!'"

},

When we push our repo to Heroku, Heroku will run npm install while deploying the app to make sure all necessary dependencies are installed. With the scripts above, we are telling Heroku to run the postinstall script after it initially runs npm install . The postinstall script runs the build script which will run yarn install in the client directory to install any dependencies the client may have, and then run yarn build to create the build folder that will hold the static files. Then the deploy script will run and will copy all the build files into the public folder for deployment.

Heroku also needs to know your version of Yarn, NPM, and Node. To find them use these commands in the terminal:

yarn -v && npm -v && node -v

Then add an engines object to the Rails’ package.json :

. . . "scripts": {

"build": "cd client && yarn install && yarn build && cd ..",

"deploy": "cp -a client/build/. public/",

"postinstall": "yarn build && yarn deploy && echo 'Client built!'"

},

"engines": {

"yarn": "1.21.1",

"npm": "6.11.3",

"node": "12.11.1"

},

With the above instructions, Heroku now knows how to build and deploy the app. We now need to tell it how to run the Rails server once the app is hosted on Heroku. To do this, we need to give it instructions via a Procfile:

In the app’s root directory, create a file called Procfile (it must be named exactly this way). In the file, write the following line:

web: bundle exec puma -t 5:5 -p ${PORT:-3001} -e ${RACK_ENV:-development}

This tells Heroku to run the Rails server on port 3001.

Note: You can set this port to whatever you want. Just make sure it is not the same port your client is using to connect, and that is the same port your fetch/ajax request are using to fetch data from the backend. More on this in the section below.

Connecting the React Client to the Rails API

By default, the Rails server runs on port 3000. So does the client development build when ruining in your local server, i.e. when running yarn start or npm start .

You might have been running two local servers by executing rails s –p 3001 in your terminal and on your app’s root directory, and yarn start on the client’s root directory. This will allow your client to run on the http://localhost:3000/ and the Rails backend/server on http://localhost:3001/.

This manual setup is ok while on development, but for production, we need to make sure that the client makes the server calls to the right port, every time. To do so, we must add a proxy to the client’s package.json file:

{

"name": "client",

"version": "0.1.0",

"private": true,

"proxy": "http://localhost:3001",

"dependencies": {



. . .



}

Next, we need to make changes to the client’s fetch requests.

You may have a component that makes calls to the Rails API/server. Something similar to this:

const apiURL = 'http://localhost:3001' export const fetchWorkouts = () => {

let data = {

method: 'GET',

headers: {

'Accept': 'application/json',

'Content-type': 'application/json'

}

} return dispatch => {

fetch(`${apiURL}/resources`, data)

.then(response => response.json())

.then(resources => dispatch({

type: 'FETCH_RESOURCES',

resources

}))

.catch(errors => errors)

}

}

A few things to note here. First, this is a React/Redux “API call action”. It uses ‘Redux-Thunk’ to handle the fetch request, but the same principles apply to use other tools like ‘Axios’ or vanilla fetch requests.

Second, note that here we have an apiURL variable that points to the Rails server. The variable is then interpolated into the fetch request address where it’s used to call on the “resources” endpoint. Although this is the correct way to handle these calls on your development environment, this wouldn’t work for a production build when deployed to Heroku. This is because we are “hard-coding” the path to our server, and Heroku –after deployment and once the app has been built on their server, uses its own HTTP routing stack with unique paths to the app’s Heroku server.

To fix this, you simply remove the apiUrl variable from the fetch request path and leave your resource endpoint. Something along:

//const apiURL = 'http://localhost:3001' export const fetchWorkouts = () => {

let data = {

method: 'GET',

headers: {

'Accept': 'application/json',

'Content-type': 'application/json'

}

} return dispatch => {

fetch(`/resources`, data) //removed hard-coded path

.then(response => response.json())

.then(resources => dispatch({

type: 'FETCH_RESOURCES',

resources

}))

.catch(errors => errors)

}

}

One last thing to consider, is the use of ‘React-Router’.

If your React client uses ‘react-router’ for its routing, you would need to make some changes to the Rails backend. Specifically, you will need to create a ‘fallback route’ for all routes or paths that don’t match or are not included in your client’s routes. In other words, you will tell your app to serve the client’s index.html for any path that is not included in your client’s routes.

To do so, in the Rails app/application_controller.rb file, add the following:

class ApplicationController < ActionController::API def fallback_index_html

render :file => 'public/index.html'

end



end

And in the Rails config/routes.rb file, add the following:

get '*path', to: "application#fallback_index_html", constraints: ->(request) do

!request.xhr? && request.format.html?

end

All right, that was a lot to do! But now our application is ready to be deployed. Next, we’ll create the Heroku app and deploy our Rails with React app to it.

This will be a good time to commit your work.

Deploying to Heroku

As a reminder, I’m assuming you have a Heroku account and have the Heroku CLI installed. If you haven’t, please go to https://www.heroku.com/, create an account and follow instructions on how to set up the CLI.

The first thing to do is to log in to your Heroku account. In your terminal, execute heroku login and fill in your credentials.

Now, create a new Heroku app:

In your terminal, and in your app’s root directory, execute the following: heroku create your-app-name

You can confirm the new Heroku app is connected to your Rails + React app by executing git remote –v . This will list all your remote depositories associated with your app directory. You should see Heroku listed there because Heroku uses your git repo to build your app on their server.

Next, you will tell Heroku how to build the app. Since the app uses Node.js and Ruby on the stack, Heroku needs to execute the correct buildpacks when building the app. To do so, execute the following in your terminal:

heroku buildpacks:add heroku/nodejs --index 1

heroku buildpacks:add heroku/ruby --index 2

You are all set!!

Commit all your changes and push them to Heroku:

git add .

git commit -m "first Heroku deploy "

git push heroku master

Note: the git push command takes in two arguments, a remote name and the local branch name to be pushed, i.e git push <remotename> <branchname> . You should push the branch name where all these changes took place. If you followed my suggestion to create a new deployment branch before making these changes, you will need to deploy that branch instead of master above.

When you push your app to Heroku, Heroku will run the scripts we set in the Rails package.json file to build the app. This process will take a minute or two. You can see the build and deploy scripts running in the background while this process takes place.

You probably have some .env variables in your development. Since these variables are usually protected and not committed to git , Heroku won’t know about them when we push the project to its master branch. To add these variable to your Heroku project, we can run the following command in the terminal AFTER the application has been created and uploaded to Heroku:

heroku config:set VARIABLE=VALUE

And finally!… since this is a brand new app hosted in Heroku, there won’t be a database set yet. You must create it, migrate it, and seed it (if you have a seed file). In your terminal:

heroku rake db:create

heroku rake db:migrate

heroku rake db:seed

And that’s it! You should now have a working production app hosted in Heroku.

You can open and test the app by running heroku open on the root directory.

One final note: if you find that your app’s images or styling aren’t loading properly, it may be because their corresponding static assets aren’t being processed while deploying. Check the Heroku logs to see which assets aren’t loading.

The quick fix is to pre-compile your assets before deployment. Note, however, that doing the following will slow down the app’s loading time. It’s recommended instead that you host the static assets on a CDN service.

To pre-compile assets, change the following lines in the Rails config/environments/production.rb file:

config.assets.complie = true