Step-by-Step Guide to Hosting and Deploying a React Static Website on AWS
Tags: react, aws, cloud, infrastructureascode
Published: 2023-04-29 22:32:43

Throughout my years as a software developer specializing in frontend technologies, I've admired the simplicity of deploying static HTML, CSS, and JavaScript files to cloud hosting providers like Netlify, Vercel, or GitHub Pages. These platforms serve as Content Delivery Networks (CDNs) and automatically generate live URLs for hosted websites. Previously, I would use shared hosting or rent a Virtual Private Server (VPS) from providers like GoDaddy. Then, I would upload static files to the hosting server using an FTP client like Filezilla. Additionally, I needed to register a domain, or my website URL would default to the hosting server's IP address.

Some organizations utilize cloud services like Amazon S3 or Azure Blob storage to store and serve files. However, this approach requires additional configuration, such as proper access permissions, MIME types, and cache headers.

In a previous article on AWS CloudFormation, I suggested creating a static website hosted on Amazon S3 and distributed using Amazon CloudFront, using CloudFormation to provision and configure the S3 bucket and CloudFront distribution as a practice exercise.

Building on that idea, I've developed this tutorial to guide you through hosting and deploying a React static website on AWS using services like S3, CloudFront, and CloudFormation. If you're unfamiliar with Amazon CloudFront, it's a content delivery network (CDN) service designed to enhance web applications' performance, availability, and security by distributing content to users from geographically closer edge locations.

### How would we build this?

This tutorial will use Amazon S3, Amazon CloudFront, and AWS CloudFormation to deploy a React-based static website. 

S3 will be used to store the compiled static website files generated from the React project. 

To enhance the performance and global reach of the website, we will use CloudFront CDN service to cache and serve content to users from edge locations closer to them.

AWS CloudFormation will be the backbone of this setup, as it allows us to define the infrastructure for our static website as code. Creating a CloudFormation template will automate the provisioning and configuration of the S3 bucket and CloudFront distribution.

### What’s the purpose?

Using this combination of services, we are taking advantage of the scalability, security, and reliability offered by the AWS ecosystem. With the static website hosted on S3 and distributed through CloudFront, users will experience faster load times and better performance. At the same time, managing the infrastructure with CloudFormation ensures that the entire setup can be version-controlled, easily updated, and maintained systematically.  

In other words, we are building a mini Vercel or Netlify for deploying static React apps. 

## Pre-requisites

It would be best if you had the following:

- AWS Account: Sign up for an AWS account if you don't have one already.
- AWS CLI: Install and configure the AWS CLI on your local machine. For instructions, follow this guide: **[https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html)**
- Code editor (preferably VS Code)
- Basic Knowledge of AWS CloudFormation
- Enable [AmazonS3FullAccess](https://us-east-1.console.aws.amazon.com/iam/home#/policies/arn:aws:iam::aws:policy/AmazonS3FullAccess) and [AWSCloudFormationFullAccess](https://us-east-1.console.aws.amazon.com/iam/home#/policies/arn:aws:iam::aws:policy/AWSCloudFormationFullAccess) Permission for your IAM user.
- [Nodejs ≥v16](https://nodejs.org/en/download) installed

Let’s get started.

## Creating a React Project for AWS Deployment

I found a beautiful todo app by **[Ilyas Belaoud](https://github.com/ilyasbelaoud)** on [GitHub](https://github.com/ilyasbelaoud/react-todo-app), and we will use it for this project. To get started, clone the repository:

```bash
git clone https://github.com/ilyasbelaoud/react-todo-app.git
```

Install the dependencies by running this command:

```bash
npm install 
```

When it’s done installing, run the app with this command:

```bash
npm run start
```

The app will start at `[http://localhost:3000](http://localhost:3000)` on your browser. You should see something like this: 

![Screenshot 2023-04-29 at 12.40.52.png](https://res.cloudinary.com/sammy365/image/upload/v1682805975/Screenshot_2023-04-29_at_12.40.52_iivru9.png)

Great! We’ve set up the React app. 

## How to Set Up an Amazon S3 Bucket for Your React Website

Let’s go ahead and create the S3 bucket to host the React app. 

### **Write the CloudFormation template to create an S3 bucket**

To create an S3 bucket using CloudFormation, you must define the bucket resource in a YAML or JSON template file. In this example, I'll use YAML.

Create a file called `s3-react-static-website.yaml` and add these lines of code:

```yaml
Resources:
  StaticWebsiteBucket:
    Type: 'AWS::S3::Bucket'
    Properties:
      BucketName: <random-unique-string>
```

This template defines a single S3 bucket resource named "StaticWebsiteBucket" with the specified bucket name. Replace `my-static-website-bucket` with a unique bucket name of your choice.

### **Configure the bucket to act as a static website host**

To configure the S3 bucket for hosting a static website, add the `WebsiteConfiguration` property to the bucket definition in the CloudFormation template.

```yaml
Resources:
  StaticWebsiteBucket:
    Type: 'AWS::S3::Bucket'
    Properties:
      BucketName: <random-unique-string>
			PublicAccessBlockConfiguration:
        BlockPublicAcls: false
        BlockPublicPolicy: false
        IgnorePublicAcls: false
        RestrictPublicBuckets: false
      WebsiteConfiguration:
        IndexDocument: index.html
        ErrorDocument: 404.html
```

Here, we've specified `index.html` as the index document and `404.html` as the error document. Adjust the file names according to your project structure.

The `PublicAccessBlockConfiguration` property is important because all Block Public Access settings are automatically enabled when a new bucket is created. So for the purpose of this demo, we are setting them to false.

### **Set up appropriate bucket policies for public access**

To grant public read access to the objects in your S3 bucket, you need to create an S3 bucket policy. Add the following resource to your CloudFormation template:

```yaml
Resources:
  # ... (previous resources) ...

  StaticWebsiteBucketPolicy:
    Type: 'AWS::S3::BucketPolicy'
    Properties:
      Bucket: !Ref StaticWebsiteBucket
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal: '*'
            Action:
              - 's3:GetObject'
            Resource: !Sub 'arn:aws:s3:::${StaticWebsiteBucket}/*'
```

This policy allows public `GetObject` access to all objects in the bucket. The `!Ref` function is used to reference the bucket created earlier, and the `!Sub` function is used to substitute the bucket name into the resource ARN.

This is the complete Yaml code:

```yaml
AWSTemplateFormatVersion: '2010-09-09'
Description: 'Create an S3 bucket for hosting a static React website'

Resources:
  StaticWebsiteBucket:
    Type: 'AWS::S3::Bucket'
    Properties:
      BucketName: my-static-website-bucket
			PublicAccessBlockConfiguration:
        BlockPublicAcls: false
        BlockPublicPolicy: false
        IgnorePublicAcls: false
        RestrictPublicBuckets: false
			WebsiteConfiguration:
        IndexDocument: index.html
        ErrorDocument: 404.html

  StaticWebsiteBucketPolicy:
    Type: 'AWS::S3::BucketPolicy'
    Properties:
      Bucket: !Ref StaticWebsiteBucket
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal: '*'
            Action:
              - 's3:GetObject'
            Resource: !Sub 'arn:aws:s3:::${StaticWebsiteBucket}/*'
```

## Amazon CloudFront Setup for React Static Website

In this section, we will set up the CloudFront distribution. These are the steps we will take:

- Write the CloudFormation template to create an S3 bucket
- Configure the distribution to use the S3 bucket as the origin
- Set up caching, SSL, and other settings as needed

Let’s get started.

### **Write the CloudFormation template to create a CloudFront distribution**

To create a CloudFront distribution using CloudFormation, you must define the distribution resource in the YAML template file. Update the existing YAML file that you used to set up the S3 bucket:

```yaml
Resources:
  # ... (previous resources) ...

  StaticWebsiteCloudFrontDistribution:
    Type: 'AWS::CloudFront::Distribution'
    Properties:
      DistributionConfig:
        Enabled: true
        DefaultRootObject: index.html
        Origins:
          - DomainName: !GetAtt StaticWebsiteBucket.RegionalDomainName
            Id: StaticWebsiteS3Origin
            S3OriginConfig:
              OriginAccessIdentity: ''
```

This template defines a single CloudFront distribution resource named `StaticWebsiteCloudFrontDistribution`. The distribution is enabled and configured to use the S3 bucket as its origin.

Let me break down the relevant parts of this code snippet.

- The `Properties` section is where you define the properties of the CloudFront distribution. The `DistributionConfig` property contains the configuration settings for the CloudFront distribution.
- The `Enabled` property is set to "true", which means that the CloudFront distribution is active.
- The `DefaultRootObject` property is set to `index.html`. This means that when a user accesses the root of your CloudFront distribution, CloudFront will return the `index.html` file.
- The `Origins` property lists origins for the CloudFront distribution. In this case, there's only one origin: the S3 bucket you created earlier for hosting the static website.
    - `DomainName`: The domain name of the S3 bucket. `!GetAtt StaticWebsiteBucket.RegionalDomainName` is a CloudFormation function that retrieves the regional domain name of the S3 bucket you created earlier.
    - `Id`: A unique identifier for the origin, in this case, "StaticWebsiteS3Origin".
    - `S3OriginConfig`: Configuration settings specific to the S3 origin. `OriginAccessIdentity` is set to an empty string, meaning the CloudFront distribution will use the S3 bucket's public access settings.

### **Configure the distribution to use the S3 bucket as the origin**

In the YAML file, we've already configured the CloudFront distribution to use the S3 bucket created earlier as its origin. The `!GetAtt` function is used to get the regional domain name of the S3 bucket.

### **Set up caching, SSL, and other settings as needed**

For caching and other settings, you can configure the `CacheBehaviors` and `DefaultCacheBehavior` properties of the distribution. You can add an ACM (AWS Certificate Manager) certificate ARN to the `ViewerCertificate` property to enable SSL. However, seeing as we don’t have a  custom domain, we will not include the `ViewerCertificate`. 

Does this mean the website connection will be unsafe using HTTP? No, it doesn’t. The CloudFront distribution will use the default CloudFront domain (e.g., `d12345abcd12345.cloudfront.net`). The connection will still be secure using HTTPS with the default CloudFront certificate, but the URL will not use a custom domain.

Update your Yaml file with these lines of code:

```yaml
Resources:
  # ... (previous resources) ...

  StaticWebsiteCloudFrontDistribution:
    Type: 'AWS::CloudFront::Distribution'
    Properties:
      DistributionConfig:
        Enabled: true
        DefaultRootObject: index.html
        Origins:
          - DomainName: !GetAtt StaticWebsiteBucket.RegionalDomainName
            Id: StaticWebsiteS3Origin
            S3OriginConfig:
              OriginAccessIdentity: ''
        DefaultCacheBehavior:
          AllowedMethods:
            - GET
            - HEAD
          CachedMethods:
            - GET
            - HEAD
          Compress: true
          ForwardedValues:
            QueryString: false
            Cookies:
              Forward: none
          MinTTL: 3600
          MaxTTL: 86400
          DefaultTTL: 86400
          TargetOriginId: StaticWebsiteS3Origin
          ViewerProtocolPolicy: redirect-to-https
```

This configuration sets up caching, enables Gzip compression, and enforces HTTPS. Let’s break down some of the relevant parts of this code snippet. You can notice some properties you’ve used previously repeated, right? I think this is how you get to learn faster. 

- The `DefaultCacheBehavior` property is used to configure how CloudFront processes requests and caches content. In this case, the `AllowedMethods` property specifies which HTTP methods are allowed for requests. We only allow "GET" and "HEAD" methods since it's a static website; these are the only methods needed to fetch content.
- The `CachedMethods` property lists the HTTP methods for which CloudFront caches the responses. We cache `GET` and "HEAD" responses to serve content faster to users from edge locations.
- The `Compress` property is set to `true`, which enables automatic Gzip compression of files. This reduces the transmitted files' size, resulting in faster load times and reduced data transfer costs.
- The `ForwardedValues` property specifies how CloudFront forwards query strings and cookies to the origin (the S3 bucket in this case). In this configuration, we set `QueryString` to `false`, meaning CloudFront does not forward query strings to the origin. We set Forward to none for the `Cookies` property, indicating that CloudFront does not forward any cookies to the origin.
- These properties set the Time-to-Live (TTL) values for cached objects in seconds: `MinTTL` is the minimum amount of time that objects stay in the cache, `MaxTTL` is the maximum time, and `DefaultTTL` is the default time if no other value is specified in the cache-control header from the origin. In this configuration, the minimum TTL is set to 1 hour (3600 seconds), the maximum TTL is set to 24 hours (86400 seconds), and the default TTL is also set to 24 hours (86400 seconds).
- The `TargetOriginId` property specifies the origin (in this case, the S3 bucket) to which CloudFront forwards requests. The value "StaticWebsiteS3Origin" matches the "Id" value specified in the "Origins" property earlier.
- The `ViewerProtocolPolicy` property sets how CloudFront handles HTTP and HTTPS requests from users. In this case, "redirect-to-https" means that CloudFront automatically redirects HTTP requests to HTTPS, ensuring users access the site securely.

So far, this is how our Yaml file looks like:

```yaml
AWSTemplateFormatVersion: '2010-09-09'
Description: 'AWS CloudFormation Template: Static Website with React, Amazon S3, and Amazon CloudFront'
Resources:
  StaticWebsiteBucket:
    Type: 'AWS::S3::Bucket'
    Properties:
      BucketName: your-unique-bucket-name
			PublicAccessBlockConfiguration:
        BlockPublicAcls: false
        BlockPublicPolicy: false
        IgnorePublicAcls: false
        RestrictPublicBuckets: false
      WebsiteConfiguration:
        IndexDocument: index.html
        ErrorDocument: error.html

  StaticWebsiteBucketPolicy:
    Type: 'AWS::S3::BucketPolicy'
    Properties:
      Bucket: !Ref StaticWebsiteBucket
      PolicyDocument:
        Id: PublicReadForWebsiteContent
        Version: '2012-10-17'
        Statement:
          - Sid: PublicRead
            Effect: Allow
            Principal: '*'
            Action: 's3:GetObject'
            Resource: !Sub 'arn:aws:s3:::${StaticWebsiteBucket}/*'

  StaticWebsiteCloudFrontDistribution:
    Type: 'AWS::CloudFront::Distribution'
    Properties:
      DistributionConfig:
        Enabled: true
        DefaultRootObject: index.html
        Origins:
          - DomainName: !GetAtt StaticWebsiteBucket.RegionalDomainName
            Id: StaticWebsiteS3Origin
            S3OriginConfig:
              OriginAccessIdentity: ''
        DefaultCacheBehavior:
          AllowedMethods:
            - GET
            - HEAD
          CachedMethods:
            - GET
            - HEAD
          Compress: true
          ForwardedValues:
            QueryString: false
            Cookies:
              Forward: none
          MinTTL: 3600
          MaxTTL: 86400
          DefaultTTL: 86400
          TargetOriginId: StaticWebsiteS3Origin
          ViewerProtocolPolicy: redirect-to-https
```

## Deploying Your CloudFormation Stack on AWS

This section will deploy the cloud formation stack using the AWS CLI. 

Follow these steps:

- Run the following AWS CLI command to create the CloudFormation stack:
    
    ```bash
    aws cloudformation create-stack --stack-name <YourPreferredStackName> --template-body file://s3-react-static-website.yaml
    ```
    
    It might take some minutes to deploy, depending on your internet connection. 
    
- To verify the stack creation progress, run the following command:
    
    ```bash
    aws cloudformation describe-stacks --stack-name <YourStackName>
    ```
    
    If it’s deployed successfully, you should see this on your terminal:
    
    ![Screenshot 2023-04-29 at 21.13.57.png](https://res.cloudinary.com/sammy365/image/upload/v1682805976/Screenshot_2023-04-29_at_21.13.57_jv2rzk.png)
    
    You can also verify from your cloud formation console:
    
    ![Screenshot 2023-04-29 at 21.14.39.png](https://res.cloudinary.com/sammy365/image/upload/v1682805975/Screenshot_2023-04-29_at_21.14.39_c6inov.png)
    

Awesome!

## Building and Uploading Your React App to S3

Now that your CloudFormation stack has been successfully deployed and the S3 bucket and CloudFront distribution are set up, it's time to build and upload your React app.

### **Build the React app**

Navigate to your React app's root directory in your terminal and run the following command to create a production-ready build of your React app:

```bash
npm run build
```

This command generates a `build` folder in your project directory containing the minified and optimized files necessary for deploying your React app.

![Screenshot 2023-04-29 at 22.32.59.png](https://res.cloudinary.com/sammy365/image/upload/v1682805975/Screenshot_2023-04-29_at_22.32.59_losjjv.png)

### **Upload the contents of the `build` folder to the S3 bucket**

Let’s now use the AWS CLI to upload the build folder contents to the s3 bucket. 

Navigate to the `build` folder in your React app's directory. Run the following command to sync the contents of the `build` folder to your S3 bucket:

```bash
aws s3 sync . s3://your-bucket-name
```

You can find your bucket name in your s3 dashboard. You should get something like this:

![Screenshot 2023-04-29 at 22.26.37.png](https://res.cloudinary.com/sammy365/image/upload/v1682805975/Screenshot_2023-04-29_at_22.26.37_w5okqw.png)

After uploading the contents of the `build` folder to the S3 bucket, your React static website should be accessible through the CloudFront distribution URL. You can find the URL in the Amazon CloudFront console under the "Domain Name" column for your distribution.

![Screenshot 2023-04-29 at 22.38.25.png](https://res.cloudinary.com/sammy365/image/upload/v1682805975/Screenshot_2023-04-29_at_22.38.25_hhbs2y.png)

And yes, it works!

![Screenshot 2023-04-29 at 22.39.09.png](https://res.cloudinary.com/sammy365/image/upload/v1682805976/Screenshot_2023-04-29_at_22.39.09_l67wdk.png)

## Leveraging AWS for Frontend React Projects: Final Thoughts

In this tutorial, we've gone through the step-by-step process of hosting and deploying a React static website on AWS using S3, CloudFront, and CloudFormation. We've learned to create and configure an S3 bucket for hosting our website files, set up a CloudFront distribution to optimize performance, and used CloudFormation to automate the provisioning and management of these resources.

By leveraging these AWS services, we've successfully deployed a scalable, high-performance, and globally accessible React static website. We've also familiarized ourselves with the AWS ecosystem, gaining valuable skills and knowledge that can be applied to future projects.

This tutorial serves as a solid foundation for more advanced use cases, enabling you to harness the full potential of AWS CloudFormation Infrastructure as Code Service.

## Additional Resources for Further Learning

- [AWS CloudFormation Guide on AWS::S3::Bucket](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3-bucket.html#aws-properties-s3-bucket--examples)
- **[Blocking public access to your Amazon S3 storage](https://docs.aws.amazon.com/AmazonS3/latest/userguide/access-control-block-public-access.html)**
- [AWS CloudFront Developer Guide](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/Introduction.html)