How to Preserve SPA route path in the browser using AWS CloudFront
Tags: aws, cloudfront, angular, react
Published: 2022-08-07 13:33:50

---
title: How to Preserve SPA route path in the browser using AWS CloudFront
published: true
description: Solving the problem CloudFront routing of Single Page Applications 
#canonical_url:
tags: AWS, CloudFront, Angular, React
cover_image: https://cdn-images-1.medium.com/max/800/0*TFuVkgU1YtUzJwLy
# Use a ratio of 100:42 for best results.
---

The other day I noticed a very irritating behavior on my personal website that I built using Angular. When I type in my domain name in the browser URL bar
![website url in browser url bar](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/45j5wx7x9pn025ciblmt.png)

I get redirected to `elbanhawy.com/home` and can see my website homepage load. That's not the annoying behavior, that the expected part. What I found out by accident was that if I tried to reload that page or enter a specific path on my website like `elbanhawy.com/blog` I get an Access Denied response from AWS!
![Access Denied Page from S3](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/eq5ofbqekxmx8b276cs5.png)

What's happening here??

This might be a common shocker to anyone who's never deployed an SPA like React, Vue, and Angular on AWS S3 and CloudFront before so let's see how I tackled this issue.

## Step 1: Debugging Checklist
### Checking S3 bucket policies and the CF Origins and OAI configurations

My angular site is hosted on an S3 bucket which is not public. My CloudFront distribution serves the S3 bucket content, so my S3 bucket policies should explicitly allow my CloudFront distribution to access all content inside the bucket. Let's take a look:
```json
{
    "Version": "2008-10-17",
    "Id": "PolicyForCloudFrontPrivateContent",
    "Statement": [
        {
            "Sid": "1",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity E3LLNG3QXXXXX"
            },
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::my-angular-website/*"
        }
    ]
}
```

The bucket policy above explicitly allows my CloudFront OAI that I have associated with my CloudFront distribution to access/get all the objects inside the S3 bucket. I take note of that OAI ID E3LLNG3QXXXXX which I will need later.
I double check in my CloudFront distribution Origins tab to see if my S3 bucket is listed as an origin.
![List of origins in CLoudFront console](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e3xxt8xih1v6idz231l2.png)
I see that it is so I choose it and click "Edit" to see its configuration details:
![My S3 origin Edit configurations](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lhl5dbosl47sx32b56fg.png)
I make sure that under S3 bucket access section that the "Yes use OAI" option is selected. 

Now I take note of the OAI name selected above. I go to CloudFront's console (CF/Security/Origin access identities) to see my listed OAIs. Remember that OAI ID I took note of from the S3 bucket policy? Now I need to make sure that it is present in the list.
![List of Origin access identities in CloudFront console](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ylhoqwzdqvh2el96t7i9.png)
I see that it is so I can safely assume that the CloudFront distribution that serves my website has correct access to my S3 bucket.

**Okay…So what else can be wrong??**

##Step 2: Solution
At this point I'm scratching my head and trying to read countless AWS docs for hints without success. But then I spot an interesting tab in my CloudFront's distribution page.
![Error pages tab in CloudFront console](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/upzmrqggozc8ghmdmykb.png)
Come to think of it, when I enter my root domain I didn't get any errors initially. It is only when I reload or include a specific route in my URL that I get an Access Denied error, which translates to a **403** error code.

So I decide to click "Create custom error response" and add an entry:
![Creating a custom error response in CloudFront console](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1jzqr4i0uvy4smb1kh19.png)
I choose **403:Forbidden** from the list of error codes, then select "Yes" to Customize error response and then, and this is the critical part, I enter "/index.html" as the **Response page path** and 200: OK as the **HTTP Response code**.

Now if I enter my blog path URL in the browser and hit enter I get served the correct page without any Access Denied error even when I reload the page!
![My blog page loading correctly](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3udiela7vv7pr4a1dst4.png)

**Note**: if you have more than one CloudFront distribution for your S3 website, you need to these steps for each one. For example, I had a separate distribution for my `www` subdomain and had to apply the solution there as well as the distribution for the root domain (`elbanhawy.com`).

##Step 3: Reflection
I've solved the problem but what's still missing is a clear explanation of why this Access Denied error showed up in the first place.

Well this can be attributed to the way S3 buckets behave in response to requests. S3 does not understand routes. An S3 bucket is like a server that is expecting a path to a specific file stored in it. For example, I can be more explicit and add `/index.html` at the end of my domain url and I would still get the homepage. S3 understands this because it looks for a file called `index.html` in the bucket and returns it.

Originally, when I typed in my domain url `elbanhawy.com` , because of my configurations, CloudFront automatically appended `/index.html` to the request it forwarded to the S3 bucket which S3 understood and return my index.html file which had links to my compiled angular code. When loaded in the browser, the angular application takes over and the angular router takes effect.

However, when I reloaded the page or type the route in my url, CloudFront forwarded the request to S3 as-is and S3 tries to find a literal match for a file with the same route name. For example, when I tried `elbanhawy.com/blog` before the fix, S3 tried to look for a file called `blog` which does not exist so it returns an error. Since I have not originally specified how errors should be handled by CloudFront, an Access Denied error was returned to the browser.

The solution above, changed that default error behavior to always return the `index.html` file whenever that error occurs and therefor the angular site is always returned and the angular router still gets access to the route in the URL and is able to show me the correct page.

For more tips and insights on cloud and web development follow me on Twitter [@adham_benhawy](https://twitter.com/adham_benhawy).