Deploying a React app to S3

You've used React to build a web app. Now you want to share it with the world.

While there are seemingly infinite options for file hosting, it's hard to go wrong with AWS' S3 (Simple Storage Service):

  • AWS' free tier has reasonably high limits for S3. Beyond that, S3 is cheap.
  • S3 is fast and you can choose from data centers across the globe.
  • Should you decide to add an API server for your React app to talk to, AWS is the gold standard of cloud platforms.

All this means you can quickly upload your work for friends or clients to see and not pay a dime.

In this post, we'll cover:

  1. What buckets and objects are in S3
  2. How to setup a bucket for hosting a React app
  3. How to bundle and upload a React app built with create-react-app to S3
  4. How to setup s3cmd to make future deployments easier

Update 3/22/2016: Mere moments after this post went live, the AWS console received a facelift 🤦‍♂️

The screenshots might not match the console interface you're seeing, but the overall workflow is the same.

The app

We'll work with a simple app in this post. It's a hero generator. For heroes like you:

the app

You can check out the code over on GitHub. The app was created with create-react-app.

If you want to follow along at home, just clone the repo:

$ git clone [email protected]:fullstackreact/hero-generator

We've only made small additions to an app generated with create-react-app. Specifically, we've added some styles and two components:

  • App: the meat and potatoes
  • HiddenImages: A little trick to have the browser prefetch all the hero images so that switching between them is speedy

Setting up S3

First, if you don't have an account already, go create an AWS account here: https://aws.amazon.com/.

After divulging all your personal info to Amazon and answering a phone call from one of their friendly robots, visit the AWS console: https://console.aws.amazon.com. You might be prompted to sign in again.

After reaching the console, click the "Services" button in the top left:

location of the services button

This will reveal the largest drop-down you've ever seen:

aws services drop-down
The ever-growing AWS services drop-down

Locate and click on S3. That will bring you to the page https://console.aws.amazon.com/s3:

Welcome to S3

S3 buckets and objects

In S3, a bucket is a collection of objects. A bucket is a virtual container. Objects are files belonging to that container.

While there are ways to configure AWS to serve multiple websites out of a single bucket, it's easier to have a 1-to-1 relationship between buckets and sites. So, for our purposes, we'll want to create a new bucket every time we want to deploy a new React app.

Click on the "Create Bucket" button. You'll be asked to specify a bucket name and a region:

Bucket regions

You create a bucket inside a specific AWS region. AWS has datacenters around the world:

Where will your bucket have the highest quality of life?

AWS region map via https://aws.amazon.com/about-aws/global-infrastructure/

In AWS, different regions can have different prices. Our React app is less than 30MB though, so price differences won't matter. Instead, it's generally good practice to locate your bucket closest to you or your users.

AWS has a free tier for the first 12 months. Check out the details here. The limits are quite reasonable.

Bucket names

As for the bucket name, there are two things to consider:

  1. Bucket names must be globally unique across AWS
  2. If you are using a domain, you should name your bucket after your domain

As for #1, I wanted to demonstrate this by showing a screenshot of me attempting to use a bucket name that would be obviously already taken. But I guess Jeff Bezos is a busy guy and doesn't have time to be going around creating buckets? So I have his namesake bucket now...

Jeff, if you ever want this bucket it'll be in Montreal for you.

As for #2, we won't worry about this too much in this post. Instead, I'll direct you to an AWS article on the topic at the end. Just use a generic bucket name for the time being. (If you try to come up with a cool domain name right now, you will never finish this post.)

Setting up your bucket

We have our bucket. In my case, it's bezos over in Montreal:

We need to do three things:

  1. Enable static website hosting. This is why we're here, right??
  2. Set up permissions. Our bucket is locked down right now. In order for our bucket to be an effective host for our website, we need to tell AWS that we're OK with people requesting objects from the bucket.
  3. Upload our React app to the bucket.

1. Enable static website hosting

Ensure your bucket is highlighted so you can see the properties on the right-hand side. Click "Static Website Hosting" and then "Enable website hosting." Our index document will eventually be index.html, so fill in that value and click "Save":

2. Set up permissions

In S3, buckets can have different policies. The policy specifies who can do what to which objects in a given bucket.

In the case of you and your React app, you want to setup your bucket such that you are the only one that can write to it. However, you want the world to be able to view it.

To enable the world to view objects in your bucket, we'll add a bucket policy. Click on "Permissions" then "Add bucket policy":

The permissions section
Add bucket policy prompt

Policies are represented as JSON documents. We'll use the following policy. Copy and paste it into the bucket policy prompt. Just change the placeholder <BUCKET-NAME> to your bucket's name:

{
  "Version": "2012-10-17",
  "Statement":[
    {
      "Sid":"AddPerm",
      "Effect":"Allow",
      "Principal": "*",
      "Action":["s3:GetObject"],
      "Resource":["arn:aws:s3:::<BUCKET-NAME>/*"]
    }
  ]
}

Breaking this down:

  • Principal: This is the who. Using a *, we're saying everyone. Literally, everyone.
  • Action: This is what the principal can do. We're saying everyone can perform a "get."
  • Resource: This is the which. Against which resources (objects) can the principal perform this action?

So, all together, we're saying everyone can get any object in <BUCKET-NAME>.

You might be tempted to edit the Version field. Don't. The Version field is not the version of your policy but the version of AWS' policy grammar. You can read more here, but basically as of the time of this writing the value for this field will always be 2012-10-17 for new policies.

Here's the bucket policy for bezos:

Click "Save." The masses can now access our (empty) bucket!

In the future, you can always use AWS' bucket policy examples page or their policy generator should you need to create a nuanced policy.

3. Upload our React app to the bucket

We now have a bucket living in the cloud somewhere (supposedly Montreal). We've told AWS we want to use it for static website hosting. And we've set things up so that the world can access all the objects in our bucket. Now, we just need to upload our website.

First, we need the static website to upload.

In our example, we're using create-react-app. We can run the npm run build command which will create a build/ folder. That folder will be totally self-contained. Everything needed to run our app will be included.

Let's run that command now:

Taking a look inside build/:

$ ls -1p build/
asset-manifest.json
assets/
favicon.ico
index.html
static/

Under assets/ is our images folder. Inside static/ is minified versions of our CSS and JS, including all our app's components and the entire React library.

index.html is the centerpiece of our app. If you were to look at the file, you'd see that it loads all of our CSS and JavaScript files.

Remember how we instructed AWS that we'd like the index document of our static website to be index.html? That will be this document right here.

We just need to upload the contents of build/ to S3.

Over on the S3 console, we'll enter the bucket by clicking on its name. Then we'll click on "Upload":

Now, we want to upload the contents of build/ to S3. Here's an important nuance: If we upload the build folder, things won't work. S3 is expecting the index.html file at the top-level of our bucket. So we need to drag and drop the contents of the build folder, like so:

Draggin' and droppin'

Note that the upload size is 29MB. 14 of these MB is the images. If you're on a slow connection, you can consider not uploading all the images.

After clicking "Start Upload" and waiting a bit (apologies to the other patrons currently at my coffeeshop), we'll see all of our files listed in the bucket:

Our website is up in the cloud. And the world has access to it.

But ... how do we see it?

To find out, click on the index.html file. And then ensure that the right-hand view is toggled to "Properties." The full URL to this file will be listed:

Don't use the first link listed, which is this format:

// wrong URL
<bucket-region>.amazonaws.com/<bucket-name>/index.html

Instead, you're looking for this format:

// million-dollar URL
<bucket-name>.s3-website.<bucket-region>.amazonaws.com

Here's our running web app, sitting on magnetic plates somewhere in Montreal but accessible to the world:

So inspired!!

Setting up s3cmd

Dragging and dropping your website to S3 every time you make changes certainly works, but won't win you many points at cocktail parties.

We can use a command line tool, s3cmd, to impress our friends and neighbors.

AWS also provides its own CLI, which you can find instructions for installing here.

AWS' CLI operates on much more than just S3 but requires a little bit more work to setup. If you want to give it a shot, check out these setup instructions.

Get the tool here: http://s3tools.org/s3cmd. Or, if you're on OSX and have homebrew installed, you can do this:

$ brew install s3cmd

In order to use s3cmd, we need our access keys from AWS. Remember, our bucket is read-only to the world. In order to make changes to it, we have to authenticate ourselves as the owner.

To get your access keys, follow this AWS tutorial. I'll be here when you get back.

With your keys in hand, you can run this command to have s3cmd use these keys indefinitely:

s3cmd --configure

s3cmd will prompt you for your keys. Copy and paste your access key and secret key from the AWS console.

If you want to skip configuration for now, you can just use your keys during subsequent commands like this:

 s3cmd --access_key=<ACCESS-KEY> --secret_key=<SECRET-KEY> <COMMAND>

With s3cmd configured, we can verify it works by asking the tool to list all our buckets:

$ s3cmd ls
2017-03-10 17:11  s3://bezos

Cool! Now, we can use this handy command to "sync" the contents of our build folder with this bucket:

$ s3cmd sync LOCAL_DIR s3://BUCKET[/PREFIX]

In my case, that'll look like this:

$ s3cmd sync build/* s3://bezos

Any time I make changes to my React app in the future that I want to deploy, I just need to run the following commands in sequence:

$ npm run build
$ s3cmd sync build/* s3://bezos

Naturally, we can add a command to our package.json that does this for us:

// in package.json
"build-and-deploy": "npm run build && s3cmd sync build/* s3://bezos && echo '🚀  Deployed!'"

What's next?

I mentioned earlier that if you finished the post, I'd remind you about that article on AWS about using custom domains. So, reminded.

Oh, and go build something useful. ❤️🏔


Found this post helpful? Then 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.


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.