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/*"
}
]
}
And you need to enable Static website hosting:
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:
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:
It will work, but you will have below issues:
- All requests will have a 404 error code
- They will be served not from the cache (CF will request content on each request from S3)
- 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:
And it will fix the issue with the 404 error code:
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;
}
And attach it to the CloudFront:
Now you will have the correct response code, and all requests will be served from the cache:
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'
You will force CloudFront to request this date on each request:
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.
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!
Top comments (3)
Thanks for the detailed guide, everything is super clear!
Now it seems so easy, thanks Roman for clarifying this!
p.s. special thanks for the screenshots 🙌