Phil Zona Dot Net

Deploying your React app with Git

If you haven't heard about React, this is probably not the place to find out what it is and why it kicks ass. But if you have heard, you've probably slammed your head against a wall a few times trying to figure out Webpack configs and Babel presets. Unless you have really specific needs, most of us probably use create-react-app to bootstrap our React projects, at least while we're creating a proof of concept.

I've been using the tool for a project (which I'll talk about another time) and I've been thinking a lot about other ways to make my life easier. Specifically when it comes to deployment.

The problem

Heroku has been a really good tool, and this isn't about knocking what they're doing. In fact, I've learned a decent amount about Heroku over the last couple days, and it's way more powerful than I realized. I wanted to put my new React app online so I could show it off, which is actually not too difficult if you use the React buildpack, but setting up a custom domain on Heroku turned out to be a real chore. I'm already hosting a few things and I have the servers and domains, so I decided to figure out how to do set something up with what I already had.

My requirements: I wanted to host my React app myself, but I wanted deployment to be as easy as it was on Heroku.

Here's what I was doing before: creating a git repo with whatever project I wanted to host, cloning the repo into my web root, and manually logging in and pulling it any time I wanted to update it.

I know, feel free to mock, but I never had something that I've wanted to deploy this early before it was "finished," so I never gave it much thought.

The solution

It turns out this is much, much simpler than I imagined. I actually only needed one tool, and it was one that I already use: Git.

Git is distributed by nature, which a lot of beginners may not realize. What this means, in practical terms, is that you can use pretty much any Git repo to be a client or a server (or both) - it's not just a local tool for pushing to GitHub, you can actually set up your own remote repo using the same tool you already use.

What you need to recreate my setup

You'll need a few things to use the same setup I created. It turned out that I had all of these things already, but if you're starting from scratch, it's all fairly low cost.

First, you need a React app. You don't have to actually use the create-react-app tool, but you should have a project build directory that includes your optimized production files. I'm using create-react-app because I'm all about saving time.

You also need a server. I recommend Linode for hosting, although Digital Ocean and Amazon EC2 would work just fine too if you already use one of them. Full disclosure, that's a referral link.

You'll need some familiarity with Linux servers - how to move around, create files, and set up a web server like nginx. Linode has amazing documentation on these topics, and it's very beginner friendly.

Finally, you need a domain name, although I guess this isn't strictly required.

How to do it

To begin, your server should be configured with basic security - specifically, SSH keys. You should also have nginx installed, with a server block (virtual host) created. Linode has great docs on how to get to this point as well.

On the server

Once all this is done, log in to your server, and go to your web root (the location you'll be using to store your app). Usually this is /var/www but it can be wherever you like.

In that file, create two directories. One will be the name of your app, which I'll call react for the sake of example, and the other one will be react.git. They must have the same name, with one of them ending in .git.

Head into your react.git folder and initialize an empty Git repo with this command:

git init --bare

The purpose of having two directories is this: the first one, react will be where your app actually lives. The second, react.git will be your remote Git server that you push to when you deploy. The reason we separate them out is because of some weird behavior in Git - a repo can't receive pushes to a branch that's currently checked out, by default, so we need to have one location that handles all the Git stuff, and another for our actual code. That's a bit of a simplification, but hopefully it makes sense.

In order to make these two play nicely together, we'll create a new hook. Hooks are scripts that do things when something happens, and what we want ours to do is populate the react folder when you push to the Git repo.

Go into the react.git folder, and go into the hooks directory within it (this was created when you initialized the repo). Create a file called post-receive and enter the following code:

#!/bin/bash
TARGET="/var/www/react"
GIT_DIR="/var/www/react.git"
BRANCH="master"

while read oldrev newrev ref
do
	# only checking out the master (or whatever branch you would like to deploy)
	if [[ $ref = refs/heads/$BRANCH ]];
	then
		echo "Ref $ref received. Deploying ${BRANCH} branch to production..."
		git --work-tree=$TARGET --git-dir=$GIT_DIR checkout -f
	else
		echo "Ref $ref received. Doing nothing: only the ${BRANCH} branch may be deployed on this server."
	fi
done

This hook is triggered when you push to the repo, and it populates the react folder with the contents of the push, i.e. your code. Make sure to change the TARGET and GIT_DIR variables to the directories you created above.

In your nginx configuration, make sure your root is set to the build directory within your react folder. Look for these lines:

root /var/www/react/build;
        index index.html index.htm;

Restart nginx to apply your changes, and you're all set!

On your local machine

The server is good to go, now we just need to enable an easy way to push from your local computer. We'll start by setting up the remote repo. Navigate to the project you want to push to the server, and add the remote:

git remote add prod user@server:/var/www/react.git

You'll need to change a few things in this command for your setup, so let's look at what values you should substitute with your own:

  • prod - The name of the remote repo. I'm treating mine like a production server, so that' why I called it prod, but this could be whatever you want, like the name of your app.
  • user - Your username that you use to log in to your server
  • server - Your server's hostname. If you set up DNS for a domain, that's what this is. You could technically use your IP address here also.
  • /var/www/react.git - This needs to be the absolute file path of the repo on your server.

Finally, remove the build/ directory from your project's .gitignore file. This isn't always best practice, but it's required for our setup.

Time to deploy your React app

You're all set! Make sure you commit any changes, and run your build script. If you're using create-react-app, this is yarn build (or npm run build).

To deploy to your server, simply push it like you would with Heroku, but to your own remote repo instead:

git push prod master

Give it a couple seconds to receive your push, visit your site in a browser, and you should see your React app, live on the world wide web for anyone to use.

An important note that I often forget - you need to build your app any time you want to push it. You can make all the commits you want, and develop new features constantly without building. But if you want to deploy it, you first need to run your build script since that's what is being served.

Why this works

The reason this works for React apps is because when you build them, you're creating a static page. Sure, there's a TON of JavaScript code that actually gets run, but that's all it is - a script.

When not to use this setup

This would not work with React as the front end to a larger project (i.e., Rails or Django) without some serious modification. You'd also need some additional setup on the server if you wanted to use this method of pushing with a different type of app that requires some sort of handler - Python and PHP are going to be no-gos if you want to run this setup exactly how I described it.

Recap

This was an insanely frustrating problem for so long, but like a lot of problems, the solution seems simple when you already know it. So now that I've got it up and running, I feel pretty good.

There's a lot more to deployment than what I've created, of course. This isn't meant to be an all-encompassing solution for every use case, but it's a really neat solution for getting static sites up and running in a super easy way.

The biggest lesson from this is that new tools aren't always the answer. When I first set out to create a simple deployment setup, I came across a half dozen services that looked like they'd do the job, but they were all complete overkill. It's a symptom of the level of advancement in technology today, and it's not necessarily a bad thing. But as you get deeper into the world of EaaS (everything as a service - sorry, dumb joke), remember that these services are built on existing ones. And even as a beginner, starting with the basics is usually a more effective way to learn.

If you found this helpful, or have trouble getting it to work, hit me up on Twitter!