Hosting a static react website on Amazon S3 with CDK

-

I recently migrated an existing website to the cloud and decided to share the process.

The cloud platform I used for this task was Amazon Web Services (AWS). In AWS there is a service called S3 (Simple Storage Service). S3 “Buckets”, which are similar to file folders, store objects, which consist of data and descriptive metadata. Why are we using S3 Buckets to host our website you ask? Because it is cheap!

AWS charges customers for storing objects in a bucket and for transferring objects in and out of buckets, this is perfect for a static website that doesn’t have to change much. The average price of hosting a website on an S3 bucket is $0.50 per month when using the free tier. When outside the free tier, it will cost up to $1-3 per month.

So hosting a website on Amazon S3 is a simple and cost-effective way to make your website available to the world. In this blog post, we’ll discuss the steps of hosting a React website on Amazon S3 with the use of the AWS Cloud Development Kit (CDK), which is a software development framework to define cloud infrastructure as code.

You can check out the result here

Why use AWS CDK?

The main reason for using the AWS CDK is to enable developers to define and manage their cloud infrastructure using familiar programming languages and tools, rather than having to write templates in JSON or YAML. This provides several benefits, including improved efficiency, increased agility, better collaboration, and improved security. Overall, the AWS CDK provides a flexible, powerful, and efficient way to define, provision, and manage cloud infrastructure, helping organizations to be more productive and successful in their cloud computing efforts.

Requirements

The requirements of the migrating assignment were:

  • The site redirects from HTTP to HTTPS
  • using a CloudFront distribution
  • and ACM certificate
  • move the existing domain name to AWS

Let’s get started

Before you start, ensure you have an AWS account and a React app you want to host. The code uses the AWS Cloud Development Kit (CDK), which is a software development framework to define cloud infrastructure as code.

We start by importing the necessary constructs, services, and utilities from the AWS CDK library.

import { Construct } from "constructs";
import * as s3 from "aws-cdk-lib/aws-s3";
import * as iam from "aws-cdk-lib/aws-iam";
import * as cloudfront from "aws-cdk-lib/aws-cloudfront";
import { RemovalPolicy, Stack, StackProps } from "aws-cdk-lib";
import * as s3deploy from "aws-cdk-lib/aws-s3-deployment";
import { HttpMethods } from "aws-cdk-lib/aws-s3";

The StaticSite class extends the Stack class from the AWS CDK library, which is used to create a CloudFormation Stack. The class takes three parameters: the scope of the construct, the ID of the construct, and the properties of the Stack (in this case, StaticSiteProps).

The StaticSiteProps interface extends the StackProps interface and includes an additional parameter: cloudfrontCertArn. This parameter is used to configure the CloudFront distribution, which serves as the primary access point to the static site.

export interface StaticSiteProps extends StackProps {
  cloudfrontCertArn?: string;
}

export class StaticSite extends Stack {
  constructor(scope: Construct, id: string, props: StaticSiteProps) {
    super(scope, id, props);
   // put your infrastructure here

}

In the constructor, we create an S3 bucket to store the site files. The bucket is set up to prevent public access and has a removal policy of DESTROY, which means that the bucket and its contents will be deleted when the Stack is deleted.

const websiteBucket = new s3.Bucket(this, "WebsiteBucket", {
  bucketName: `my-website-bucket`,
  publicReadAccess: false, // no public access, user must access via cloudfront
  removalPolicy: RemovalPolicy.DESTROY,
  autoDeleteObjects: true,
  cors: [
    {
      allowedHeaders: ["*"],
      allowedMethods: [HttpMethods.GET],
      allowedOrigins: ["*"],
      exposedHeaders: [],
    },
  ],
});

Then we create an Origin Access Identity (OAI), which is a special CloudFront user that is used to grant access to the objects in the S3 bucket. The OAI is granted access to the objects in the S3 bucket through an IAM policy statement.

const identity = new cloudfront.OriginAccessIdentity(this, "id");

websiteBucket.addToResourcePolicy(
  new iam.PolicyStatement({
    actions: ["s3:GetObject"],
    resources: [websiteBucket.arnForObjects("*")],
    principals: [
      new iam.CanonicalUserPrincipal(
        identity.cloudFrontOriginAccessIdentityS3CanonicalUserId
      ),
    ],
  })
);

Next, we create the CloudFront distribution, which serves as the primary access point to the static site. The CloudFront distribution is set up to redirect all traffic from HTTP to HTTPS and is configured to use the ACM certificate specified by the cloudfrontCertArn property. This ACM certificate had to be made manually before using it in the code.

const distribution = new cloudfront.CloudFrontWebDistribution(
  this,
  "cloudfront",
  {
    originConfigs: [
      {
        s3OriginSource: {
          s3BucketSource: websiteBucket,
          originAccessIdentity: identity,
        },
        behaviors: [
          {
            viewerProtocolPolicy:
              cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
            allowedMethods: cloudfront.CloudFrontAllowedMethods.GET_HEAD,
            compress: true,
            isDefaultBehavior: true,
          },
        ],
      },
    ],
    viewerCertificate: {
      aliases: ["search-matrix.luminis.amsterdam"],
      props: {
        acmCertificateArn: props.cloudfrontCertArn,
        sslSupportMethod: "sni-only",
      },
    },
    defaultRootObject: "index.html",
    errorConfigurations: [
      {
        errorCode: 403,
        responseCode: 200,
        responsePagePath: "/index.html",
      },
    ],
  }
);

Finally, the code deploys the site files to the S3 bucket using the BucketDeployment class from the AWS CDK. The site files are sourced from the ./front-end/build directory and are deployed to the websiteBucket created earlier in the code.

new s3deploy.BucketDeployment(this, "DeployWebsite", {
    sources: [s3deploy.Source.asset("./front-end/build")],
    destinationBucket: websiteBucket,
    distribution,
    });
}

Conclusion

In conclusion, this code sets up a static site infrastructure in AWS, using the AWS CDK. The infrastructure includes an S3 bucket to store the site files, a CloudFront distribution to serve the site, and an IAM policy to grant access to the objects in the S3 bucket. The site files are deployed to the S3 bucket using the BucketDeployment class from the AWS CDK.

And that’s it! You have your website up and running in the cloud, but what if you wanted to make a change to your website? Do you have to do everything over again? No! This is where Continuous Integration and Deployment (CI/CD) comes into play. Setting up a CI/CD pipeline in AWS will be explained next time!