Hosting a single page application with S3 and CloudFront

Curtis Hughes
7 min readMay 22, 2019

--

This article will outline the resources and steps necessary to host a single page application (SPA) within AWS. It assumes that you have already created an AWS account and have a basic understanding of how to use the platform.

There are actually quite a few articles that already attempt to outline this process. However, many of those articles provide misconceived or outdated information. In this article I hope to provide you with the most up to date, concise, and secure method for hosting your applications.

This process has been explicitly tested with React JS and Vue JS, but should be applicable to other SPA frameworks like Angular JS and Nuxt.

Architecture

Overview of resources

  • S3 Bucket
  • CloudFront Distribution
  • CloudFront Origin Access Identity

S3 Bucket

The S3 bucket will act as your origin server and hold all of your distribution artifacts (aka your front-end build).

To create an S3 bucket sign in to the AWS Management Console and open the Amazon S3 console at https://console.aws.amazon.com/s3/.

Choose Create bucket.

Enter a bucket name and click Create.

Open the newly created bucket and upload your distribution artifacts (Note: because we are hosting a SPA it is important that the index.html file is within the root directory of the bucket).

That’s it! Notice how we didn’t need to use a specific bucket name or configure the bucket for public hosting 🧐? That is because we will be distributing our content through CloudFront instead of allowing users to directly access objects through S3.

CloudFront Distribution

The CloudFront distribution will act as your content delivery network (CDN) by

“delivering your content through a worldwide network of data centers called edge locations. When a user requests content that you’re serving with CloudFront, the user is routed to the edge location that provides the lowest latency (time delay), so that content is delivered with the best possible performance.”
What Is Amazon CloudFront? — Amazon CloudFront

To create a CloudFront Distribution open the Amazon CloudFront console at https://console.aws.amazon.com/cloudfront/.

Choose Create Distribution.

Create a Web distribution by selecting Get Started.

There are a lot of options on the next page so I will outline the necessary fields for syncing with your S3 bucket and then show you the other configuration options I use when setting up distributions. These setting will vary depending on your physical location and the requirements of your application.

Necessary Configurations:

  • Origin Domain Name: Select the S3 bucket we created in the last section
  • Restrict Bucket Access: Yes
  • Origin Access Identity: Create a New Identity
  • Grant Read Permissions on Bucket: Yes, Update Bucket Policy
  • Default root object: index.html

Other Configurations:

  • Viewer Protocol Policy: Redirect HTTP to HTTPS
  • Compress Objects Automatically: Yes
  • Price Class: Use Only U.S., Canada and Europe

Finally select Create Distribution at the bottom of the page.

Error Responses

We need to configure one more thing before the CloudFront distribution is ready. Single page applications only have a single entry point (index.html). They handle routing and other page logic through JavaScript, which means that when a user goes directly to, or refreshes the page on, a non-root url we want them to pull down the index.html file instead of a file stored at the url path location (like in a statically hosted website).

The typical way to handle this logic is by creating custom error responses in CloudFront.

To create a custom error response open the Amazon CloudFront console at https://console.aws.amazon.com/cloudfront/

Select your CloudFront distribution and open the Distribution Settings.

Open the Error Pages tab.

Choose Create Custom Error Response.

You will need to configure two custom error responses (one for 404 Error Codes and one for 403 Error Codes).

They will show up on the error page after you create each one.

CloudFront Origin Access Identity

The Origin Access Identity (OAI) is a virtual user identity that is used to fetch private objects within your origin server (S3 bucket) to be cached by your CloudFront distribution (hense the name ‘origin’ access identity 😉). This is essential when making your S3 bucket private.

We actually created the OAI in the previous step when configuring the CloudFront distribution. However, you should verify everything was properly configured.

Verify the OAI was created successfully:

Open the Amazon CloudFront console at https://console.aws.amazon.com/cloudfront/

Select Origin Access Identity under the Security tab on the left hand side of the page.

You should see an OAI with a comment about your S3 bucket. Take note of the ID field.

Verify the OAI is linked to the CloudFront Distribution

Open the Amazon CloudFront console at https://console.aws.amazon.com/cloudfront/

Select your CloudFront distribution and open the Distribution Settings

Open the Origins and Origin Groups tab. Verify the Origin Access Identity field of the origin matches your OAI.

Verify the OAI is linked to the S3 Bucket:

Open the Amazon S3 console at https://console.aws.amazon.com/s3/.

Select the S3 bucket we created earlier.

Open the Permission tab and select Bucket Policy. You should see a policy allowing the OAI read access to your S3 bucket (verify the ID matches with your OAI).

{
"Version": "2008-10-17",
"Id": "PolicyForCloudFrontPrivateContent",
"Statement": [
{
"Sid": "1",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity E36XQ5FOD4Z4VA"
},
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::test-9db3a/*"
}
]
}

Deployments

To deploy changes to your application you simply need to update or replace the files in your S3 bucket. However, it is important to note that CloudFront will cache the items within your S3 bucket based on the TTL that you provided during the CloudFront distribution configuration. This means that if you update an object in S3 it wont be propagated to CloudFront until the TTL expires. A common way to “flush” the cache is to run a CloudFront invalidation on every file within the S3 bucket for each deployment, but Amazon actually recommends utilizing versioned file names to update existing content.

Versioned file names

I won’t go into the specifics of how to implement versioned file names because that would be a completely different topic, however I do want to address an issue I ran into when trying to utilize this method for deployments.

Note: a lot of boilerplate projects like CreateReactApp and VueCLI use WebPack to hash most file names by default.

Because we setup the CloudFront distribution’s Default root object to point to index.html we can’t easily version this file without also updating the CloudFront configuration. We could attempt to run an invalidation only on the index.html file, however we would still run into an issue with browsers caching the file locally based on the cache header. Instead the solution is to use the Cache-Control headers to specify that this file should not be cached at all. This does mean that CloudFront will always go to the origin server for the index.html file, however this shouldn’t impact performance because this file is typically very small and only used to reference other larger files that will be appropriately cached.

Below is an example of how a deployment can be run using the aws-cli. It removes all objects from the S3 bucket and then uploads the new distribution artifacts with a standard cache-control value. Then it copies the index.html file back into the bucket with the cache-control header set to no-cache.

$ aws s3 sync --delete /path/to/dist s3://<bucket-name> --cache-control max-age=31536000$ aws s3 cp --metadata-directive REPLACE --cache-control no-cache s3://<bucket-name>/index.html s3://<bucket-name>/index.html

To manually set the cache-control headers for the index.html object in S3 open the Amazon S3 console at https://console.aws.amazon.com/s3/.

Select your S3 bucket and click on the index.html file.

Open the Properties tab and select Metadata.

Click + Add Metadata and set the Cache-Control header to no-cache.

Congratulations!

You have successfully setup hosting for your single page application (Note: CloudFront distributions can take a while to be completely deployed ⏳. You can check the state of the deployment from the CloudFront distributions page).

To view your application, navigate to the Domain Name specified on your distribution.

If you liked this article make sure to checkout my other article on how to automate this process using AWS CDK!!

--

--