How to Deploy React App (any static website) to S3 and CloudFront?¶
- You can find the source code for this video in my GitHub Repo.
Intro¶
In this tutorial, we will create React App from scratch and declare a few routes using React Router. First, we will deploy React app to S3 using AWS Console and enable website hosting. We will also set up a custom domain to point to the S3 bucket. This approach won't allow us to secure our website with HTTPS. To use TLS, we would need to deploy our app to CloudFront, which is a content delivery network operated by Amazon Web Services. In addition, to the standard features of CDN, it can automatically compress files from the S3 using gzip and Brotli to improve performance and reduce the cost of the hosting in general. If you host your domain outside of AWS, you will only be able to set up a custom subdomain. For example, instead of devopsbyexample.io, you can only use www.devopsbyexample.io. If you want to use the root domain, I'll show you how to transfer your domain to the AWS Route53 and set up an alias for CloudFront distribution. Finally, we will create CI/CD pipeline using GitHub Actions to test and deploy our app to S3 and CloudFront. We will create a dedicated IAM user with permissions to upload files and invalidate cache in CloudFront.
Create React App¶
We will quickly create a simple react app for this tutorial by using a create-react-app tool. In addition, we will import React Router and define a couple of routes. I discovered when you deploy complex apps to hostings such as GitHub Pages, Firebase, and others, you need to make sure that all the pages work as expected. Sometimes you may face issues with redirects. React Router will help us validate primary use cases in the CloudFront environment. Since we will create a CI/CD pipeline at the end of the video, we would need to take care of installing dependencies as well.
Before you start, you need to have NodeJS installed on your computer. You can follow this instruction to install it. New NodeJS versions come with the npx package manager that helps you to install dependencies and run commands even if you haven't installed that module yet. Let's generate React app.
To run it, you need to switch to the React folder and run the npm start command. It should open a browser with the default page.
Next, we need to install React Router.
We need to create two routes for our application. Let's call them expenses
and invoices
and place them under src/routes/
folder.
This example is straight from the React Router tutorial.
src/routes/expenses.js | |
---|---|
src/routes/invoices.js | |
---|---|
Now, let's update App.js
to include links.
Also, we need to update index.js
to wrap the content in BrowserRouter
.
Deploy React App to AWS S3 Bucket¶
When you configure your bucket as a static website, the website is available at the AWS Region-specific website endpoint of the bucket. These URLs return the default index document that you configure for the website. You can also set up a custom domain.
First of all, let's create an S3 bucket. When you choose a name for the bucket, you want to match it with a custom domain that you want to use. In my case, it's www.devopsbyexample.io. Right now, I use google domains to host my domain; that's why I can only use the subdomain www. In case I would want root domain devopsbyexample.io, I would need to transfer it to Route53; we are going to do it later.
- Give it the name www.devopsbyexample.io and choose the region.
- To host the website in the S3 bucket, you also must disable Block all public access. On the other hand, you can keep it on if you want to host it only via CloudFront. We will discuss differences later.
-
The next step is to build and upload the static files to this bucket. Go to the react project and run the build command.
-
For this example, we will use AWS Console to upload files. Later on, we will install and use aws cli in the CI/CD pipeline.
-
Then we need to enable Static website hosting and use index.html for the index document.
-
The last change we need to make before we can access the website is to allow read access to anyone. Go to the Permissions tab and update the Bucket policy. Replace www.devopsbyexample.io with your bucket name.
PublicReadGetObject | |
---|---|
- If you go to bucket endpoint right now, it should return your home page.
Setup Custom Domain for S3 Static Website¶
Most likely, you would want to add your custom domain to your website. It's relatively easy to do for subdomains if you host your domain outside of AWS. Only www or other subdomains will work. A CNAME cannot be placed at the root domain level because the root domain is the DNS Start of Authority (SOA) which must point to an IP address.
- Go to your domain hosting provider and create a CNAME record pointing to the S3 bucket endpoint (CNAME must match the bucket name). In my case it will look like this:
NAME TYPE VALUE
------------------------------------------------------------------------------------------
www.devopsbyexample.io. CNAME www.devopsbyexample.io.s3-website-us-west-1.amazonaws.com.
Usually, it takes a few minutes to update the DNS. Wait a minute or so and try to use your domain to access the website.
Use http://
, HTTPS is only supported for CloudFront.
Deploy React App to CloudFront¶
It's time to deploy it to AWS CloudFront. I'll show you two approaches. One is to keep public access to all the files in the S3 bucket and use its endpoint as an origin. In my experience, this approach works better with custom routing if you have many different URLs and custom redirects. For the second approach, we will use Origin access identities to only allow CloudFront to access S3 objects and use a bucket as an origin.
- Go to CloudFront and create a new distribution.
- For the origin, paste the bucket endpoint URL. In my case,
http://www.devopsbyexample.io.s3-website-us-west-1.amazonaws.com
. - Keep
Compress objects automatically
to improve performance and reduce the cost of the hosting. - Select
Redirect HTTP to HTTPS
forViewer protocol policy
. It's a standard approach. Unless you have a special requirement, stick with it. - Add the custom domain names that you use in URLs. For example, you can add
www.devopsbyexample.io
anddevopsbyexample.io
. - If you want to use HTTPS, you need to request a certificate.
- Add the same domain names for the certificate request.
- You need to manually create CNAME records to pass the verification process.
- Wait till the certificate is valid and select it under
Custom SSL certificate
. - For the
Default root object
, enterindex.html
. - When the deployment is completed, you can access
Distribution domain name
to get your website. Now you can usehttps://
. - To set up a custom domain, you can update the
www
subdomain to point to the CloudFront distribution name. - You can also inspect the encoding header to make sure it's using a compression algorithm.
Transfer Domain to Route53¶
If you want to use the root domain for your website, you would need to transfer your domain to Route53 and set up an alias.
- Create Route53 public-hosted zone for your domain. It should match your domain name, in my case,
devopsbyexample.io
. - Then you need to update DNS Name Servers for your domain. Most of the hosting providers allow you to use custom-name servers. Find the way to update them and use name servers presented in your Route53 hosted zone.
- This procedure may easily take a few hours to complete. You can use the dig command to validate the name servers. Don't proceed until you see the correct values under NS records.
- Now, we need to create DNS records for both the www and root domain in the Route53 hosted zone. Use Aliases in both cases. If you use CNAME, this introduces a performance penalty since at least one additional DNS lookup must be performed to resolve the target.
Setup CI/CD Pipeline for React App using GitHub Actions¶
It's time to automate the deployment process. We will use GitHub Actions to install dependencies, run some unit tests, upload files to S3 and invalidate the cache.
- Let's create an empty private GitHub repository and call it
www.devopsbyexample.io
. - Add the remote origin for the React project.
- Create GitHub Actions workflow.
- To grant access for GitHub Actions to upload files to S3, we need to create an IAM user and grant appropriate permissions.
Let's create
S3WebAccess
IAM policy. Replacewww.devopsbyexample.io
with your bucket name.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ListObjectsInBucket",
"Effect": "Allow",
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::www.devopsbyexample.io"
},
{
"Sid": "AllObjectActions",
"Effect": "Allow",
"Action": "s3:*Object",
"Resource": "arn:aws:s3:::www.devopsbyexample.io/*"
},
{
"Sid": "InvalidateCF",
"Effect": "Allow",
"Action": "cloudfront:CreateInvalidation",
"Resource": "*"
}
]
}
- Create
github-actions
user and attachS3WebAccess
policy. - Go to GitHub repo and create
AWS_ACCESS_KEY_ID
andAWS_SECRET_ACCESS_KEY
secrets. - Make any change in the source code, commit and push.
Restrict Direct Access for S3 Objects (Optionally)¶
In case you want to restrict direct access for individual S3 objects in your bucket. You can block public access and grant access only to CloudFront.
- Go to your bucket and disable
Static website hosting
. - Under the
Permissions
tab, enableBlock public access
and removeBucket policy
. - Go to
CloudFront
and update the origin to point to the S3 bucket.