If you have ever set up static site hosting using S3 Bucket, your website URL probably looks somewhat like the following: example.com/index.html
There is nothing wrong with the URL except one tiny problem. It does not look beautiful at all. Ideally, the site should be reachable at example.com. But now, the user has to append index.html to the end of the URL to see the webpage.
The easy solution is to set CloudFront DefaultRootObject to index.html. So whenever the user requests example.com, CloudFront returns index.html at the root of your S3 Bucket.
├── blog
│ ├── my-first-blog
│ │ ├── index.html
│ ├── my-second-blog
│ │ ├── index.html
├── index.html
Given the folder structure above, you can reach example.com just fine. However, you cannot reach example.com/blog/my-first-blog at all. This is because S3 Bucket interally is a key value store, and there is no object with the name blog/my-first-blog in S3 Bucket.
To solve the problem above, we need to make use of CloudFront Function
Using CloudFront Functions, we can implement default directory indexes for all bucket directories. At the time of writing, there are some limitations with CloudFront Functions, notably that it only supports JavaScript ES 5.1.
Make sure that your function is triggered on viewer request
event.
The following code does the trick:
/**
* The function should be triggered on CloudFront Viewer Request event
* handler is the entry point
* Assume that if the last segment of the URL contains a .
* (dot), it is requesting a file
*/
function handler(event) {
var response = {
statusCode: 301,
statusDescription: "Moved Permanently",
headers: {},
cookies: {},
};
var host = event.request.headers.host.value;
// if the requested resource is not a file,
// redirect to trailing slash
if (!haveTrailingSlash(event.request.uri) &&
!haveFilePostfix(event.request.uri)) {
response.headers = {
'location': {
"value": `https://${host}${event.request.uri}/`
}
}
return response;
}
// Redirect index.html to / to avoid duplicate contents
if (haveIndexHtmlPostfix(event.request.uri)) {
var updatedUri = event.request.uri.slice(0, -"index.html".length)
response.headers = {
'location': {
"value": `https://${host}${updatedUri}`
}
}
return response;
}
if (haveTrailingSlash(event.request.uri))
event.request.uri += "index.html";
return event.request;
}
function haveFilePostfix(requestUri) {
var lastSegment = requestUri.split("/").pop();
if(!lastSegment)
return false;
return lastSegment.includes(".");
}
function haveTrailingSlash(requestUri) {
return requestUri.endsWith("/");
}
function haveIndexHtmlPostfix(requestUri) {
var lastSegment = requestUri.split("/").pop();
return lastSegment === "index.html" ? true : false;
}
To summarize, there are 3 use cases:
- example.com/blog/index.html is redirected to example.com/blog/
- example.com/blog/ maps to blog/index.html file that is located in S3 Bucket
- example.com/blog is redirected to example.com/blog/ (Add trailing slash)
The code allows:
- Pretty URLs for our site (aka directory URLs)
- Avoid having duplciate contents on the website. Such as example.com/blog/index.html should redirect to example.com/blog/. Having many duplicate contents on a webiste affect our SEO ranking.
Limitations:
- Need to assume that every file contains
.
symbol (extension), and directory cannot contain.