Deploying a static website using S3 and CloudFront

Deploying a static website using S3 and CloudFront - AWS CDK

Single Page Apps or SPAs are quite popular nowadays and developing a nice frontend using frontend frameworks like Angular or React is a breeze. However, when it comes to deploying these apps to a production environment (the cloud), things can quickly become complicated, as topics like high availability, cost optimisation, performance (low latency), caching, come into play.

For a frontend developer with limited cloud expertise, this can be a problem. Luckily, AWS CDK makes things so much easier from an operational perspective. However, knowing how to build the right blocks is still a requirement. Let me try to help.

In this example, we’ll create a regular S3 bucket that will store the static files of our frontend (think of the /dist folder of Angular, where the compiled output of the frontend app is emitted as artifacts). That bucket will be “private”, meaning that it will be inaccessible directly from the internet.

Instead, we’ll use CloudFront as the entrypoint to the files within the bucket. By using CloudFront, we take advantage of its high availability, global distribution of edge locations (that’s because it’s a CDN service) and caching settings.


Let’s start by creating a simple website inside the /dist folder:

/dist/index.html

<html lang="en">
<head>
<title>Hello world</title>
</head>
<body>
<h1>
Welcome to my website
</h1>
</body>
</html>

Creating the S3 bucket

From the infrastructure (AWS CDK) side, let’s create the S3 bucket that will hold the website:

import {Bucket, BucketAccessControl} from "@aws-cdk/aws-s3";
const bucket = new Bucket(this, 'Bucket', {
accessControl: BucketAccessControl.PRIVATE,
})

Note: Do not use the websiteIndexDocument or websiteErrorDocument props of the Bucket construct if you follow this example closely. Passing these will cause the Bucket to be automatically configured as “website enabled”, which makes the CloudFront examples below stop working.

Deploying the static website to the S3 bucket

How do we upload the /dist folder (our website’s file) to the S3 bucket? We could do it through the AWS CLI, the AWS Console or another manual mechanism, but personally I prefer to automate stuff as much as possible. CDK already provides a built-in mechanism to auto-upload a directory to an S3 bucket. It’s called the BucketDeployment construct. Here’s an example of how to use it:

import {BucketDeployment, Source} from "@aws-cdk/aws-s3-deployment";
import * as path from "path";
new BucketDeployment(this, 'BucketDeployment', {
destinationBucket: bucket,
sources: [Source.asset(path.resolve(__dirname, './dist'))]
})

Make sure to adjust the path to your dist folder if needed.

Now, when you try to browse the URL of the bucket, e.g. https://awscdks3staticwebsitestack-bucket83908e77-h0p2uea4pavv.s3-eu-west-1.amazonaws.com/, you should be faced by an AccessDenied error, which indicates that we have correctly configured our S3 bucket from a privacy perspective. People can not access it directly by randomly guessing its URL.

Creating the CloudFront distribution that serves the files from the S3 bucket

Let’s now create a “public” entrypoint to our bucket – the CloudFront distribution:

import {Distribution, OriginAccessIdentity} from "@aws-cdk/aws-cloudfront";
import {S3Origin} from "@aws-cdk/aws-cloudfront-origins";
const originAccessIdentity = new OriginAccessIdentity(this, 'OriginAccessIdentity');
bucket.grantRead(originAccessIdentity);
new Distribution(this, 'Distribution', {
defaultRootObject: 'index.html',
defaultBehavior: {
origin: new S3Origin(bucket, {originAccessIdentity}),
},
})

Remember to pass the OriginAccessIdentity to the S3Origin, as well as grant it permissions to the S3 bucket, because this makes the CloudFront service be able to securely read and serve files from the private S3 bucket.

Now, when we deploy everything, get the Cloudfront URL (e.g. https://d1c91p94te958.cloudfront.net) and try to access it in the browser, we should be able to see our website.

Happy coding!

Need help? Get in touch