Production-Ready AWS CloudFront for SPA

roman_abdulmanov

Roman Abdulmanov

Posted on February 3, 2023

Production-Ready AWS CloudFront for SPA

In this article, we will discuss the basic scenario for using CloudFront to serve a static SPA.

As always, we will try to make it not just work but to achieve the maximum performance available in AWS. So, I'd like to start with a basic scenario and then try to improve it.

Static files

If you work in the AWS environment, you usually upload static files to S3.

You need to configure Bucket Policy to enable public access (without authorization):

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "PublicReadGetObject",
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::my-website-bucket-static/*"
        }
    ]
}
Enter fullscreen mode Exit fullscreen mode

And you need to enable Static website hosting:

Image description

And that's it! Now your site is available by the url:
http://<bucket-name>.s3-website-<region>.amazonaws.com/

HTTPS

The apparent drawback of the current solution is the lack of HTTPS. So let's set up CloudFront to resolve this.

Technically we only need to specify the correct CloudFront Origin:

Image description

When the distribution is deployed, your site will be available by CloudFront DNS name, e.g.:
https://21nnj7ewt8yqwc.cloudfront.net

The most important advantage of using CF is that you will get a full-fledged CDN, i.e., your static files will be delivered from the location closest to you, which means that the delays in the website loading will be minimal.

SPA

You will soon notice that if you have SPA, routing will not work. E.g.:
https://21nnj7ewt8yqwc.cloudfront.net/test/route
Because CloudFront will try to redirect requests to the test/route folder, which does not exist in S3.

SPA: First attempt to fix

In some posts or answers, you may find the following solution: configure Error document for S3 Static website hosting to redirect failed attempts back to index.html:

Image description

It will work, but you will have below issues:

  1. All requests will have a 404 error code Image description
  2. They will be served not from the cache (CF will request content on each request from S3) Image description
  3. S3 will request the content twice, first at the path specified in the request, and after an error, index.html itself

SPA: Second attempt to fix

The most popular solution that you could find on the internet: configure CloudFront with Custom Error Response. In our case, it would be:
Image description

And it will fix the issue with the 404 error code:

Image description

But unfortunately, you will still have the issue with the cache and two requests. The reason for this is simple: CloudFront will request the page from S3, and when it receives a 404, it will return index.html from the cache. So you will lose all the advantages of CDN.

SPA: Solution

To fix both issues, we can implement CloudFront Function and fix routing for our SPA.

Just create a new Function:

function handler(event) {
  var request = event.request;
  var uri = request.uri;
  if (uri.endsWith('/')) {
    request.uri = '/index.html';
  } 
  else if (!uri.includes('.')) {
    request.uri = '/index.html';
  }
  return request;
}
Enter fullscreen mode Exit fullscreen mode

And attach it to the CloudFront:

Image description

Now you will have the correct response code, and all requests will be served from the cache:

Image description

There are no penalties for launching these functions. They run in the exact location where the CDN runs.

S3 cache policy

You can configure some cache parameters on the S3 level. But you should do this very carefully. For example, if you take example from popular S3 sync plugin for Serverless Framework and upload index.html with below configuration:

params:
  - index.html:
    CacheControl: 'no-cache'
Enter fullscreen mode Exit fullscreen mode

You will force CloudFront to request this date on each request:

Image description
Which, firstly, will work slowly, and secondly, it will load Origin with unnecessary requests.

The disadvantage of not having this setting is that you must make an invalidation request in the AWS console or via CLI / API to update the cached content.

Image description

API

If you have an API inside AWS infrastructure, please read my previous posts about Origin Shield and GraphQL.

By combining these approaches, you will achieve the best performance results and a higher level of security.

Thanks!

💖 💪 🙅 🚩
roman_abdulmanov
Roman Abdulmanov

Posted on February 3, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related