I have an Angular app's static files being served on an S3 bucket through Cloudfront. My Cloudfront distribution has error pages set up so it still renders the Angular's index.html. This means that if I request <cloudfront-distribution>.cloudfront.net/home-page
, instead of saying that it didn't find a file named home-page
on the S3 bucket, it will still render the angular app and the angular app will handle that /home-page
route.
I needed to include some security headers on the app server so I set up a Lambda@Edge function to inject those headers on a viewer response event (like described here https://aws.amazon.com/blogs/networking-and-content-delivery/adding-http-security-headers-using-lambdaedge-and-amazon-cloudfront/).
The Lambda@Edge is working for routes that actually correspond to a file in the S3 bucket (if I have a file called image.png
on the root folder of my S3 bucket, and I request <cloudfront-distribution>.cloudfront.net/image.png
, I see the response headers that I injected via the Lambda@Edge function. The issue is when accessing a route that doesn't correspond to a file in the S3 bucket. If I access <cloudfront-distribution>.cloudfront.net/home-page
, S3 will return a 404, Cloudfront will handle the 404 and act accordingly to the Error Pages configuration, i.e., respond with a 200 status code and render the index.html file. And when this happens, I don't see any of the headers I injected via the Lambda@Edge function, while all the other script files of my Angular app have the headers.
How can I make all responses go throught the Lambda@Edge function?
Lambda@Edge lets you use CloudFront triggers to invoke a Lambda function. When you associate a CloudFront distribution with a Lambda function, CloudFront intercepts requests and responses at CloudFront edge locations and runs the function. Lambda functions can improve security or customize information close to your viewers, to improve performance.
When Lambda returns an invalid response to CloudFront, error messages are written to log files which CloudFront pushes to CloudWatch in the Region of where the Lambda function executed. It's the default behavior to send the log files to CloudWatch when there's an invalid response.
If you use Lambda@Edge functions, you can use graphs in the CloudFront console to help track down what's causing the error, and then work to fix it. For example, you can see if HTTP 5xx errors are caused by CloudFront or by Lambda functions, and then, for specific functions, you can view related log files to investigate the issue.
No URL or DNS changes so end users do not know they have been redirected to an error page The CloudFront distribution was configured with two origins in a CloudFront Origin Group, the primary was the AWS Load Balancer, fronting the customer’s application dynamic and personalized content.
I just went through this exact same situation. I found this page in the AWS docs to be most helpful: https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-cloudfront-trigger-events.html
While the current answer is definitely correct for this question, I had another constraint that might be helpful to discuss:
As you observed (and called in the docs) the viewer response event are not trigger when there is as error at the origin - including when a custom error page is returned as a 200.
Option #1 - Swapping to the Origin Request event
If you're already using Lambda@Edge and don't want to change anything this is probably the simplest change.
Option #2 - Stop Using Custom Error Responses
For me, I went this route. I needed to create a second function for the Viewer Request event that re-writes any requests that are not specifically for an s3 resource so React Router paths return my index.html. (Something like this: https://stackoverflow.com/a/60012469/4413888). finally, removed my existing custom error responses in CloudFront.
Based on the answer of @philarmour I will show a concrete example for a solution.
Remove the custom 404 page that returns /index.html
in your CloudFront Distribution. Then add a new CloudFront Function (customize the code below to your needs)
var rootPaths = {
css: 1,
favicon: 1,
fonts: 1,
img: 1,
js: 1,
'robots.txt': 1,
};
function handler(event) {
var firstPart = event.request.uri.split('/')[1];
if (!(firstPart in rootPaths)) event.request.uri = '/index.html';
return event.request;
}
Associate the function with your CloudFront Distribution on VIEWER_REQUEST
and don't forget to publish it.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With