If you would like to deploy a React App to AWS S3 and AWS CloudFront, then you can follow this guide.
The following solution creates a React App and deploys it to S3 and CloudFront using the client’s CLI.
It also chains commands so that a React build, S3 sync and CloudFront invalidation can occur with a single command.
cd sample-react-app<br>npm start
```<figure class="wp-block-image size-large is-resized">
<img decoding="async" loading="lazy" src="https://ataiva.com/wp-content/uploads/2022/08/image-4-484x350.png" alt="" class="wp-image-9377" width="820" height="593" srcset="https://ataiva.com/wp-content/uploads/2022/08/image-4-484x350.png 484w, https://ataiva.com/wp-content/uploads/2022/08/image-4-300x217.png 300w, https://ataiva.com/wp-content/uploads/2022/08/image-4-768x556.png 768w, https://ataiva.com/wp-content/uploads/2022/08/image-4-1536x1112.png 1536w, https://ataiva.com/wp-content/uploads/2022/08/image-4.png 1644w" sizes="(max-width: 820px) 100vw, 820px" /> </figure>
# Install Router
Now we need to install `react-router-dom` so that we can change routes between pages in our React app.
Once this is done, we can edit our code before moving onto the deployment steps.
# Swap out the code
Open the `App.js` file under the `src` directory andreplaceall the code in the file with the following:
import ‘./App.css’;
import React from “react”;
import {
BrowserRouter as Router,
Routes,
Route,
Link
} from “react-router-dom”;
If we run the React app with`npm start`, we will now see the following:<figure class="wp-block-image size-large is-resized"><img decoding="async" loading="lazy" src="https://ataiva.com/wp-content/uploads/2022/08/image-9-484x350.png" alt="" class="wp-image-9382" width="819" height="592" srcset="https://ataiva.com/wp-content/uploads/2022/08/image-9-484x350.png 484w, https://ataiva.com/wp-content/uploads/2022/08/image-9-300x217.png 300w, https://ataiva.com/wp-content/uploads/2022/08/image-9-768x556.png 768w, https://ataiva.com/wp-content/uploads/2022/08/image-9-1536x1112.png 1536w, https://ataiva.com/wp-content/uploads/2022/08/image-9.png 1644w" sizes="(max-width: 819px) 100vw, 819px"/></figure>If we click on`About`in the navigation, the page changes and shows the`About`component.<figure class="wp-block-image size-large is-resized"><img decoding="async" loading="lazy" src="https://ataiva.com/wp-content/uploads/2022/08/image-10-484x350.png" alt="" class="wp-image-9383" width="818" height="592" srcset="https://ataiva.com/wp-content/uploads/2022/08/image-10-484x350.png 484w, https://ataiva.com/wp-content/uploads/2022/08/image-10-300x217.png 300w, https://ataiva.com/wp-content/uploads/2022/08/image-10-768x556.png 768w, https://ataiva.com/wp-content/uploads/2022/08/image-10-1536x1112.png 1536w, https://ataiva.com/wp-content/uploads/2022/08/image-10.png 1644w" sizes="(max-width: 818px) 100vw, 818px"/></figure># Setting up S3 and CloudFront in the AWS Management Console
Head over to the S3 console and`create a new bucket`.
Give it a unique`bucket name`and click`Create bucket.`<figure class="wp-block-image size-large is-resized"><img decoding="async" loading="lazy" src="https://ataiva.com/wp-content/uploads/2022/08/image-28-483x350.png" alt="" class="wp-image-9412" width="819" height="593" srcset="https://ataiva.com/wp-content/uploads/2022/08/image-28-483x350.png 483w, https://ataiva.com/wp-content/uploads/2022/08/image-28-300x217.png 300w, https://ataiva.com/wp-content/uploads/2022/08/image-28-768x556.png 768w, https://ataiva.com/wp-content/uploads/2022/08/image-28.png 1536w" sizes="(max-width: 819px) 100vw, 819px"/></figure>We now have a new bucket, with nothing inside.<figure class="wp-block-image size-large is-resized"><img decoding="async" loading="lazy" src="https://ataiva.com/wp-content/uploads/2022/08/image-29-483x350.png" alt="" class="wp-image-9416" width="824" height="597" srcset="https://ataiva.com/wp-content/uploads/2022/08/image-29-483x350.png 483w, https://ataiva.com/wp-content/uploads/2022/08/image-29-300x217.png 300w, https://ataiva.com/wp-content/uploads/2022/08/image-29-768x556.png 768w, https://ataiva.com/wp-content/uploads/2022/08/image-29.png 1536w" sizes="(max-width: 824px) 100vw, 824px"/></figure>Head over to CloudFront and `create a distribution`:<figure class="wp-block-image size-large is-resized"><img decoding="async" loading="lazy" src="https://ataiva.com/wp-content/uploads/2022/08/image-30-483x350.png" alt="" class="wp-image-9418" width="819" height="593" srcset="https://ataiva.com/wp-content/uploads/2022/08/image-30-483x350.png 483w, https://ataiva.com/wp-content/uploads/2022/08/image-30-300x217.png 300w, https://ataiva.com/wp-content/uploads/2022/08/image-30-768x556.png 768w, https://ataiva.com/wp-content/uploads/2022/08/image-30.png 1536w" sizes="(max-width: 819px) 100vw, 819px"/></figure>Select the `Origin domain`, which will be the newly created S3 bucket.
Specify a `Name`. Note that it will create one for you from the `Origin domain` bydefaultif you don’t specify one yourself.
For S3 bucket access, Choose `Yes use OAI`, create a new OAI andselect `Yes` for the `Bucket policy Update`.<figure class="wp-block-image size-full"><img decoding="async" loading="lazy" width="774" height="340" src="https://ataiva.com/wp-content/uploads/2022/08/image-14.png" alt="" class="wp-image-9387" srcset="https://ataiva.com/wp-content/uploads/2022/08/image-14.png 774w, https://ataiva.com/wp-content/uploads/2022/08/image-14-300x132.png 300w, https://ataiva.com/wp-content/uploads/2022/08/image-14-768x337.png 768w" sizes="(max-width: 774px) 100vw, 774px"/></figure><figure class="wp-block-image size-full"><img decoding="async" loading="lazy" width="774" height="340" src="https://ataiva.com/wp-content/uploads/2022/08/image-14.png" alt="" class="wp-image-9388" srcset="https://ataiva.com/wp-content/uploads/2022/08/image-14.png 774w, https://ataiva.com/wp-content/uploads/2022/08/image-14-300x132.png 300w, https://ataiva.com/wp-content/uploads/2022/08/image-14-768x337.png 768w" sizes="(max-width: 774px) 100vw, 774px"/></figure>Under `Default cache behavior`, select `Redirect HTTP to HTTPS.`Under `Settings`, specify the `Default root object` to be `index.html`Leaveall other fields asisand click`Create distribution`.<figure class="wp-block-image size-large is-resized"><img decoding="async" loading="lazy" src="https://ataiva.com/wp-content/uploads/2022/08/image-31-483x350.png" alt="" class="wp-image-9420" width="818" height="593" srcset="https://ataiva.com/wp-content/uploads/2022/08/image-31-483x350.png 483w, https://ataiva.com/wp-content/uploads/2022/08/image-31-300x217.png 300w, https://ataiva.com/wp-content/uploads/2022/08/image-31-768x556.png 768w, https://ataiva.com/wp-content/uploads/2022/08/image-31.png 1536w" sizes="(max-width: 818px) 100vw, 818px"/></figure>You will now see a distribution being created for you.<figure class="wp-block-image size-large is-resized"><img decoding="async" loading="lazy" src="https://ataiva.com/wp-content/uploads/2022/08/image-39-483x350.png" alt="" class="wp-image-9432" width="818" height="593" srcset="https://ataiva.com/wp-content/uploads/2022/08/image-39-483x350.png 483w, https://ataiva.com/wp-content/uploads/2022/08/image-39-300x217.png 300w, https://ataiva.com/wp-content/uploads/2022/08/image-39-768x556.png 768w, https://ataiva.com/wp-content/uploads/2022/08/image-39.png 1536w" sizes="(max-width: 818px) 100vw, 818px"/></figure>Note that this will take a couple of minutes to get ready,
# Setting up the Deployment Scripts
In the `package.json` file, under `src/`, locate the following `scripts` lines:
Here we will add some more options:
We will add a new script called `deploy-to-s3` and it will run the following command:
`aws s3 sync build/ s3://<your_s3_bucket_name>`Note that you can also specify an AWS_PROFILE here as follows if needed:
`aws s3 sync build/ s3://<your_s3_bucket_name>--profile <profile_name>`Update the `scripts` section to look as below, but change your own S3 bucket name inplace:
Now we need tocreate a `build` of our React app, so that we can push it’s contents to S3.
To do this, run the following command:
`npm run build`Then deploy it to S3 as follows:
`npm run deploy-to-s3`<figure class="wp-block-image size-large"><img decoding="async" loading="lazy" width="800" height="253" src="https://ataiva.com/wp-content/uploads/2022/08/image-17-800x253.png" alt="" class="wp-image-9391" srcset="https://ataiva.com/wp-content/uploads/2022/08/image-17-800x253.png 800w, https://ataiva.com/wp-content/uploads/2022/08/image-17-300x95.png 300w, https://ataiva.com/wp-content/uploads/2022/08/image-17-768x243.png 768w, https://ataiva.com/wp-content/uploads/2022/08/image-17.png 1042w" sizes="(max-width: 800px) 100vw, 800px"/></figure>Now if we look in the S3 console, we can see the files that were deloyed:<figure class="wp-block-image size-large is-resized"><img decoding="async" loading="lazy" src="https://ataiva.com/wp-content/uploads/2022/08/image-33-483x350.png" alt="" class="wp-image-9423" width="818" height="593" srcset="https://ataiva.com/wp-content/uploads/2022/08/image-33-483x350.png 483w, https://ataiva.com/wp-content/uploads/2022/08/image-33-300x217.png 300w, https://ataiva.com/wp-content/uploads/2022/08/image-33-768x556.png 768w, https://ataiva.com/wp-content/uploads/2022/08/image-33.png 1536w" sizes="(max-width: 818px) 100vw, 818px"/></figure># Setting up CloudFront pages
We now need to setup the CloudFront pages, which we will do through the CloudFront console.<figure class="wp-block-image size-large is-resized"><img decoding="async" loading="lazy" src="https://ataiva.com/wp-content/uploads/2022/08/image-34-483x350.png" alt="" class="wp-image-9424" width="818" height="593" srcset="https://ataiva.com/wp-content/uploads/2022/08/image-34-483x350.png 483w, https://ataiva.com/wp-content/uploads/2022/08/image-34-300x217.png 300w, https://ataiva.com/wp-content/uploads/2022/08/image-34-768x556.png 768w, https://ataiva.com/wp-content/uploads/2022/08/image-34.png 1536w" sizes="(max-width: 818px) 100vw, 818px"/></figure>Under the CloudFront distribution, click`Create custom error response.`We do this because React is a Single Page Application (SPA) and no physical files exist on the server for the different`Routes`that we have specified. They are all dynamic.
For example,`/about`does not exist as a logical path on the drive, or server. So instead, it will be a`404Not Found`when called upon. So therefore, we will tell CloudFront that forall`404Not Found`paths, we want`index.html`to handle them.
Remember that`index.html`is the path forwhere React initializes.
To this end, create a `404Not Found` custom error response, that points to our `/index.html` file, with a status of `200 OK`:<figure class="wp-block-image size-large is-resized"><img decoding="async" loading="lazy" src="https://ataiva.com/wp-content/uploads/2022/08/image-35-483x350.png" alt="" class="wp-image-9425" width="818" height="593" srcset="https://ataiva.com/wp-content/uploads/2022/08/image-35-483x350.png 483w, https://ataiva.com/wp-content/uploads/2022/08/image-35-300x217.png 300w, https://ataiva.com/wp-content/uploads/2022/08/image-35-768x556.png 768w, https://ataiva.com/wp-content/uploads/2022/08/image-35.png 1536w" sizes="(max-width: 818px) 100vw, 818px"/></figure>Also create a `403 Forbidden` custom error response, that points to our `/index.html` file, with a status of `200 OK:`<figure class="wp-block-image size-large is-resized"><img decoding="async" loading="lazy" src="https://ataiva.com/wp-content/uploads/2022/08/image-36-483x350.png" alt="" class="wp-image-9426" width="817" height="592" srcset="https://ataiva.com/wp-content/uploads/2022/08/image-36-483x350.png 483w, https://ataiva.com/wp-content/uploads/2022/08/image-36-300x217.png 300w, https://ataiva.com/wp-content/uploads/2022/08/image-36-768x556.png 768w, https://ataiva.com/wp-content/uploads/2022/08/image-36.png 1536w" sizes="(max-width: 817px) 100vw, 817px"/></figure>Once both have been created, the`Error pages`should have two (2) entries as follows:<figure class="wp-block-image size-large is-resized"><img decoding="async" loading="lazy" src="https://ataiva.com/wp-content/uploads/2022/08/image-37-483x350.png" alt="" class="wp-image-9427" width="818" height="593" srcset="https://ataiva.com/wp-content/uploads/2022/08/image-37-483x350.png 483w, https://ataiva.com/wp-content/uploads/2022/08/image-37-300x217.png 300w, https://ataiva.com/wp-content/uploads/2022/08/image-37-768x556.png 768w, https://ataiva.com/wp-content/uploads/2022/08/image-37.png 1536w" sizes="(max-width: 818px) 100vw, 818px"/></figure>If we don’t create these, then we will get the `AccessDenied` error when trying to access any of the `Routes` we specified in the React app, which look like this:<figure class="wp-block-image size-large is-resized"><img decoding="async" loading="lazy" src="https://ataiva.com/wp-content/uploads/2022/08/image-23-484x350.png" alt="" class="wp-image-9397" width="817" height="591" srcset="https://ataiva.com/wp-content/uploads/2022/08/image-23-484x350.png 484w, https://ataiva.com/wp-content/uploads/2022/08/image-23-300x217.png 300w, https://ataiva.com/wp-content/uploads/2022/08/image-23-768x556.png 768w, https://ataiva.com/wp-content/uploads/2022/08/image-23-1536x1112.png 1536w, https://ataiva.com/wp-content/uploads/2022/08/image-23.png 1644w" sizes="(max-width: 817px) 100vw, 817px"/></figure>Now instead, we can see the actual `Route` itself:<figure class="wp-block-image size-large is-resized"><img decoding="async" loading="lazy" src="https://ataiva.com/wp-content/uploads/2022/08/image-24-484x350.png" alt="" class="wp-image-9398" width="818" height="592" srcset="https://ataiva.com/wp-content/uploads/2022/08/image-24-484x350.png 484w, https://ataiva.com/wp-content/uploads/2022/08/image-24-300x217.png 300w, https://ataiva.com/wp-content/uploads/2022/08/image-24-768x556.png 768w, https://ataiva.com/wp-content/uploads/2022/08/image-24-1536x1112.png 1536w, https://ataiva.com/wp-content/uploads/2022/08/image-24.png 1644w" sizes="(max-width: 818px) 100vw, 818px"/></figure># Improving the Deployment scripts
Everytime we update the CloudFront distribution, by deploying new files to S3, we need to `Invalidate` the files.
Head over to the `package.json` file frombeforeandadd another command under the one we just added:
It will look something like this:
You don’t need to specify the `--profile` argument, unless you need to.
We can get the Distribution ID from CloudFront itself:<figure class="wp-block-image size-large is-resized">
<img decoding="async" loading="lazy" src="https://ataiva.com/wp-content/uploads/2022/08/image-40-483x350.png" alt="" class="wp-image-9434" width="818" height="593" srcset="https://ataiva.com/wp-content/uploads/2022/08/image-40-483x350.png 483w, https://ataiva.com/wp-content/uploads/2022/08/image-40-300x217.png 300w, https://ataiva.com/wp-content/uploads/2022/08/image-40-768x556.png 768w, https://ataiva.com/wp-content/uploads/2022/08/image-40.png 1536w" sizes="(max-width: 818px) 100vw, 818px" /> </figure>
Update this new section as follows, remember to replace your `--distribution-id`:
Now that we have both the steps we need, let’s create an aggregate command that will tie everything together, so that we only need to run a single command eachtime:
We will add the following `script`:
“deploy”: “npm run build && npm run deploy-to-s3 && npm run invalidate-cloudfront”,
This now means we have a single command to`build`our React App,`sync`the files to S3, and`invalidate`the files in CloudFront, as a chained command.
# Testing our Deployment scripts
If we take the current state of the deployed application on CloudFront, it looks like this:<figure class="wp-block-image size-large is-resized"><img decoding="async" loading="lazy" src="https://ataiva.com/wp-content/uploads/2022/08/image-26-484x350.png" alt="" class="wp-image-9400" width="817" height="591" srcset="https://ataiva.com/wp-content/uploads/2022/08/image-26-484x350.png 484w, https://ataiva.com/wp-content/uploads/2022/08/image-26-300x217.png 300w, https://ataiva.com/wp-content/uploads/2022/08/image-26-768x556.png 768w, https://ataiva.com/wp-content/uploads/2022/08/image-26-1536x1112.png 1536w, https://ataiva.com/wp-content/uploads/2022/08/image-26.png 1644w" sizes="(max-width: 817px) 100vw, 817px"/></figure>If we open the `App.js` file andcreate a new `Route`:
Now all we need to do to see the changes deployed, is run the following command:
npm run deploy
```
This will cycle through our steps and produce the following output:
```
> [email protected] deploy
> npm run build && npm run deploy-to-s3 && npm run invalidate-cloudfront
> [email protected] build
> react-scripts build
Creating an optimized production build...
Compiled successfully.
File sizes after gzip:
50.75 kB build/static/js/main.95dbd789.js
1.79 kB build/static/js/787.7c33f095.chunk.js
301 B build/static/css/main.58e1094f.css
The project was built assuming it is hosted at /.
You can control this with the homepage field in your package.json.
The build folder is ready to be deployed.
You may serve it with a static server:
npm install -g serve
serve -s build
Find out more about deployment here:
https://cra.link/deployment
> [email protected] deploy-to-s3
> aws s3 sync build/ s3://sample-react-app-123654789
upload: build/asset-manifest.json to s3://sample-react-app-123654789/asset-manifest.json
upload: build/static/js/787.7c33f095.chunk.js.map to s3://sample-react-app-123654789/static/js/787.7c33f095.chunk.js.map
upload: build/index.html to s3://sample-react-app-123654789/index.html
upload: build/robots.txt to s3://sample-react-app-123654789/robots.txt
upload: build/manifest.json to s3://sample-react-app-123654789/manifest.json
upload: build/static/js/787.7c33f095.chunk.js to s3://sample-react-app-123654789/static/js/787.7c33f095.chunk.js
upload: build/favicon.ico to s3://sample-react-app-123654789/favicon.ico
upload: build/static/css/main.58e1094f.css.map to s3://sample-react-app-123654789/static/css/main.58e1094f.css.map
upload: build/static/css/main.58e1094f.css to s3://sample-react-app-123654789/static/css/main.58e1094f.css
upload: build/logo512.png to s3://sample-react-app-123654789/logo512.png
upload: build/logo192.png to s3://sample-react-app-123654789/logo192.png
upload: build/static/js/main.95dbd789.js.LICENSE.txt to s3://sample-react-app-123654789/static/js/main.95dbd789.js.LICENSE.txt
upload: build/static/js/main.95dbd789.js to s3://sample-react-app-123654789/static/js/main.95dbd789.js
upload: build/static/js/main.95dbd789.js.map to s3://sample-react-app-123654789/static/js/main.95dbd789.js.map
> [email protected] invalidate-cloudfront
> aws cloudfront create-invalidation --distribution-id EIAUK8JFBCT6S --paths '/*'
```
Now we can refresh the browser and we will see our new `Route` added and linked to our new `TestingComponent` as soon as the CloudFront invalidations have completed.