How to deploy an SPA on CloudFront

Radu Diță
8 min readMay 5, 2020

Using a CDN to deploy your SPA will improve your APP’s availability and reduce the initial download time. Deploying an SPA using CloudFront is not especially difficult, but there are some gotchas and caveats that merit discussion.

For a deployment we need the following pieces:

  • A built Single Page App
  • An S3 bucket where to store the build files
  • A CloudFront distribution to serve the files

If we want to use a custom domain with HTTPS we’ll also need to setup:

  • A zone using Route53
  • SSL certificate using CertificateManager

This is out of scope for this article and will be discussed in a future one.

Setup

Before we get started in earnest a few definitions about the technologies we’re gonna use

S3

S3 is a storage solution from AWS. You store files using buckets. You can think of an S3 bucket as a key/value pair storage that is able to store (very) large files.

An S3 bucket has a unique name across all buckets ever created in AWS. The S3 bucket is a global service, but an S3 also lives in one of AWS region. This means that even though the service is global you must be aware of the region where your bucket is created.

CDN

CDN means Content Distribution Network. CDNs exists to allow for content to be distributed more efficiently. They usually achieve this by having multiple geographical locations (AWS calls them edges) and serve the content depending on the user’s geographical location.

How does a CDN work?

In short a CDN works by duplicating content across its network. You usually upload the content and then the CDN propagates the content across the network. By duplicating the content, different users get access to resources that are closer to them and have have a higher download speed.

CDN are best suited to distribute static content. They are usually used to distribute assets.

With the advent of SPAs, CDN are especially suited to distribute them also.

The SPA

The tutorial should work with any SPA framework, but the example we’ll be using is gonna be using Vue.js.

Most major SPA frameworks work in similar manner

  • You write your code using JS/Typescript and whatever packages you desire
  • When you’re ready to deploy you’ll do a build and end up with an index.html entry point and bundled up javascript

The demo app we’ll be using has a very simple structure

  • a main page with 2 link
  • 2 components, one for each link

We’re using vue-router with history mode enabled (no # hash in the URL)

Sample App

For our app we can generate a prod by running npm run build. This will generate a dist folder that we want to upload to an S3 bucket.

Next step is to generate the necessary infrastructure. We’ll start with the S3 bucket.

Building the infrastructure

1. S3 bucket

For creating an S3 bucket you should follow these steps

  • Go to AWS console
  • Navigate to S3
  • Click on Create bucket
  • Give it a name, this must be unique across the entire S3 ecosystem, so using a prefix is recommended. We’re naming it spa-cf-demo. This means that you cannot use this name.
  • Leave Block all public access on, as we’ll be accessing the bucket using CloudFront
Creating the S3 Bucket

2. Upload the app

Now that we have a bucket, we can upload the app to it.

There are 2 ways of doing this

We’ll be using the console

  • Go to S3
  • Click on your bucket
  • Click on Upload
  • Find your files and click Upload again
Upload SPA to S3

The app is now uploaded in S3, but it is not yet accessible. All items in the bucket are private and they should remain private. Trying to access index.html will result in an error similar to this one

S3 error page

The main reason why items in S3 are private and you have Block all public access enabled by default is so you don’t upload sensitive information by mistake and make it public.

There are other ways through which we can allow for users to access the content of the bucket. CloudFront is one of them, and we’ll set it up shortly.

3. CloudFront (CF)

Setting up a CloudFront Distribution is pretty straight forward, but there are a couple of gotchas.

To create a CF Distribution:

  • Go to CloudFront service in the AWS Console, CF is another global service, so no need to select a region
  • Click on Create Distribution
  • Click on Get Started under Web, (RTMP is approaching EOL)
  • Find your bucket under Origin Domain Name
  • After selecting your bucket a new option will appear Restrict Bucket Access
  • Choose Yes
  • Under Origin Access Identity choose Create new identity
  • Under Grant Read Permissions on Bucket also choose Yes, Update Bucket Policy
  • The above steps will create a new OAI (Origin Access Identity) and update the bucket policy to allow this OAI to access the bucket. This is what makes it possible to access the content of S3 by using CF
  • We’ll leave most of the other options as default
  • We’re gonna change Price class from Use all edges to Use only U.S., Canada and Europe. This change impacts the amount of edges CF uses. This option may not be suitable for you if you want best performance outside of those areas.
  • The last piece of information that we need to update is Default Root Object. This is the object that CF will return when we’re accessing the CF without any path after the host part.
Creating CF Distribution

At this point the CF Distribution is ready to go, but we’ll have to wait for it to propagate. Depending on the Price class this can be longer or shorter. Usually it takes between 5 and 10 minutes, so be patient.

You can now look under Distribution to see your new distribution and you’ll see the url that it was generated. It should look something like this dxxxxxxxxxx.cloudfront.net.

4. Checking the distribution

While you wait you can go to your S3 bucket to see the changes CF did on the bucket’s policy. It added the OAI, so it has access to the bucket.

Updated bucket policy

After the propagation is done you can access the provided URL to see that the SPA is running now from CF.

The importance of Default Root Object. Without setting it to index.html access dxxxxxxxxxx.cloudfront.net would result in an error, as there is no object with the key “” in the S3 bucket. But setting it means that CF will ask the Origin (S3 Bucket) for index.html instead.

You an also access dxxxxxxxxxx.cloudfront.net/index.html to see the same page working.

SPA running on CF

5. Create Custom Error Response

While navigating the app after accessing the CF url will work: if you want to share a link that contains a path, for instance dxxxxxxxxxx.cloudfront.net/path will result in a error similar to this one:

Shared link error

This happens because when a user accesses an object from CF, CF asks the Origin, S3 bucket in this case, to return an item with that particular key.

But there is no path item in S3, it is just a path that is resolved by the SPA router to a component, so you get an error.

The way to handle this is to customise the error response to point back at index.html, the core of our app that will handle it. To do this you have to:

  • Go to your distribution
  • Click on Error Page
  • Click on Create Custom Error Response
  • From the HTTP Error Code drop down choose 404
  • Click yes under Customize Error Response
  • Fill in /index.html under Response Page Path (Notice the / slash at the beginning, this is mandatory)
  • And finally choose 200: OK for HTTP Response Code
  • Click on Create and wait for the propagation to finish. Now you should be able to access the urls directly.
Adding custom error response

Extra info — future deployments

Your SPA is running nice and smoothly now under CF. It is deployed on multiple edges, close to your users. It also supports HTTPS without the need to provision a certificate. It does use cloudfront.net domain though.

At some point you may want to update the app. Your first thought would be to replace the files inside the S3 Bucket. Which is necessary, but it is not enough.

After updating the files you may notice that, accessing the CF distribution, you don’t see the changes. Why is that?

For performance reasons a CDN will cache the Origin’s (S3 bucket) content and does not hit it until the cache expires. This means that even if you push new files to S3, CF will not pick them up. To fix this you need to create an Invalidation. You do this by:

  • Go to your distribution
  • Click on Invalidations
  • Click on Create Invalidation
  • In the pop up you must enter the files you want to invalidate. For SPA that use cache busting techniques, invalidating index.html should be enough. The reason for this is that index.html is the only file that doesn’t change its name between builds. To be safe you can also invalidate all files by using /* as pattern.
  • Click on Invalidate and wait for the invalidation to finish

At this point you should see your updates.

Invalidating CF Distribution

Final thoughts

Deploying an SPA on CF is not very difficult, but there are a few steps to be performed.

Probably the most elusive one is regarding setting up custom error responses so accessing routes directly, without going thru index.html, is gonna work.

Always remember to invalidate the CF Distribution after making an update and you shouldn’t make objects public in your S3 bucket.

If you like this, please show it by clapping :)

My name is Radu and I am a co-founder of WiseUp where we build cutting edge software products.

--

--