How to handle a multilingual static site with Amazon CloudFront hosted on Amazon S3
Tags: aws, cloudfront, s3, lambda
Published: 2020-10-29 07:31:07

The easiest way to host a static website on AWS is [Amazon S3](https://aws.amazon.com/s3/). In combination with [Amazon CloudFront](https://aws.amazon.com/cloudfront/) a really fast and cost-effective solution.

But if you run a multilingual static site, you run into the following problem: Amazon CloudFront allows you to specify a default root object, such as `index.html`, but this will not work in subdirectories.

Take a deeper look into the project structure of the different applications to make them easier to understand.

**Single Page Application**

```
my-s3-bucket
├── app.css
├── app.js
└── index.html
```

**Multi Page Application**

```
my-s3-bucket
├── app.css
├── app.js
├── index.html
├── de
│   └── index.html
└── fr
    └── index.html
```

When you access your CloudFront root URL `/` in the browser, the requested `index.html` page is delivered. Everything is fine. But if you call `/de` instead of `/de/index.html` in the browser, you will get an error page with the HTTP response status code 403.

A possible solution is to change your links and add `/index.html` to each link, but I would not recommend this. All right, but which solution is the better one? Use a Lambda function!

## Use Lambda@Edge

[Lambda@Edge](https://aws.amazon.com/lambda/edge/) is a feature of Amazon CloudFront that allows your code to run globally at AWS locations close to your users. 🚀

The Lambda function is very simple. Let's have a look at the code:

```js
exports.handler = (event, context, callback) => {
  const request = event.Records[0].cf.request
  const uri = request.uri

  if (uri.endsWith('/')) {
    request.uri += 'index.html'
  } else if (!uri.includes('.')) {
    request.uri += '/index.html'
  }

  callback(null, request)
}
```

This function inspects the incoming request from the client. Then rewrites the request so that CloudFront requests a default index object (`index.html` in this case) for each request URI that ends with `/` or does not contain a dot (`.`).

❗️ The [AWS Lambda](https://aws.amazon.com/lambda/) function must be deployed in the region `us-east-1`!

**IAM**

You also need to create an IAM role that can be assumed by the service principals `lambda.amazonaws.com` and `edgelambda.amazonaws.com`. Assign this role to your Lambda function.

```json
{
   "Version": "2012-10-17",
   "Statement": [
      {
         "Effect": "Allow",
         "Principal": {
            "Service": [
               "lambda.amazonaws.com",
               "edgelambda.amazonaws.com"
            ]
         },
         "Action": "sts:AssumeRole"
      }
   ]
}
```

## Update CloudFront settings

Last but not least, it is necessary to set the default root object in the Amazon CloudFront settings to empty, as your Lambda function will handle it.

If you now call `/de` in the browser, you should see the correct page instead of the error page.

---

**Note: Serverless Framework**

As you probably noticed from my previous posts, I use the [Serverless Framework](https://www.serverless.com/) in some projects.

If your application is not deployed in the AWS region `us-east-1` you will have the following problem: The Serverless Framework does not allow you to deploy individual services in different regions. Therefore it is necessary to use two different `serverless.yml` manifests.

---

If you have any kind of feedback, suggestions or ideas - feel free to comment this post!