How-To: Setup a CloudFront Lambda@Edge function to handle Gatsby index pages
29th February 2020 - updated 14th May 2021
This is How-To guide to setup CloudFront Lambda@Edge function to handle Gatsby index pages correctly.
NOTE: In May 2021 AWS introduced CloudFront Functions. For the scenario in this article, CloudFront Functions offer an easier to manage and more cost effective solution over Lambda@Edge. Please see How-To: Setup CloudFront Function for index rewrites for more information
CloudFront has very basic handling of index.html pages, this guide shows how to add the following behaviour to CloudFront (necessary for Gatsby pages to function correctly on CloudFront):
- When a user requests a page at /my-blog-post (a directory without a trailing /) we need CloudFront to respond with a 301 response to redirect the user's browser to /my-blog-post/ (the same directory with a trailing slash /)
- When a user requests a page at /my-blog-post/ (a directory with a trailing /) we need to serve them the index.html page within that directory: /my-blog-post/index.html.
Enter Lambda@Edge
Lambda@Edge allows us to add some logic to CloudFront to modify how CloudFront handles requests to the index pages.
Creating the Lambda
- In the AWS console go to the Lambda section
- Lambda@Edge functions need to be built in the North Virginia region. Click the region in the top right menu and select
US East (N. Virginia) us-east-1
- On the main Lambda dashboard, click
Functions
on the left and then clickCreate function
(orange button) - Select
Author From Scratch
- In the Basic information section:
- Function Name: Enter
CloudFrontIndexHandler
- Runtime: leave as
Node.js 14.x
- Function Name: Enter
- In the Permissions section:
- Expand Change default execution role
- Execution role: Select
Create a new role from AWS policy templates
- Role Name: Enter
CloudFrontIndexHandler-role
- Policy templates: start typing
CloudFront
and then selectBasic Lambda@Edge permissions (for CloudFront trigger)
when you see it in the list
- Execution role: Select
- Expand Change default execution role
- Click
Create Function
at the bottom - Cut and paste the following code into the code window:
const { extname } = require('path'); exports.handler = async function(event) { const request = event.Records[0].cf.request; const requestPath = request.uri; if (looksLikeADirectory(requestPath) && !requestPath.endsWith('/')) { const pathWithSlash = `${requestPath}/`; return makeRedirect301Response(pathWithSlash, request.querystring); } if (requestPath.endsWith('/')) { const pathWithIndexHtml = `${requestPath}index.html`; return makeOriginRequestWithNewPath(request, pathWithIndexHtml); } return request; }; function makeRedirect301Response(path, queryString) { const redirectUri = combineUriParts(path, queryString); return { body: '', status: '301', statusDescription: 'Moved Permanently', headers: { location: [ { value: redirectUri, }, ], }, }; } function looksLikeADirectory(path) { const fileExtension = extname(path); return fileExtension === ''; } function makeOriginRequestWithNewPath(request, path) { return { ...request, uri: path, }; } function combineUriParts(path, queryString) { return queryString ? `${path}?${queryString}` : path; }
- Select
Save
from the File menu - Click the
Actions
button (a menu should appear) - Click
Deploy To Lambda@Edge
(if you don't see this option it's probably because you're not in the N.Virginia region, see the second step above) - In the Deploy to Lambda@Edge window:
- Distribution: if this box has something in it, click the
x
to clear it. On the list which appears click the ID for your CloudFront distribution (see Part 1 if you need to determine the ID of your CloudFront distribution) - Cache Behavior: Leave as
*
- CloudFront event: leave as
Origin Request
- Include Body: leave unticked
- Confirm deploy to Lambda@Edge: tick the confirmation checkbox
- Click
Deploy
- Close the window
- Distribution: if this box has something in it, click the
If you're interested to see how the Lambda function was linked to the CloudFront distribution you can do the following:
- In the AWS console go to the CloudFront section
- Select the appropriate Distribution in the list
- Click the
Behaviours
tab - Tick the single checkbox in the behavious table and then click
Edit
- Scroll down and you should see the link to the Lambda
Also we created an IAM role which gives the Lambda limited permissions to access various other AWS resources. If you're interested in reviewing the newly created role, do the following:
- Go to the IAM section of the console
- Click
Roles
- Click
CloudFrontIndexHandler-role
in the list - Click the
Access Advisor
tab and you should see the role grants permission to create CloudWatch log entries
- Click
So how does this work?
The code above will be run each time CloudFront tries to fetch a file from the S3 bucket (the Origin of the CloudFront distribution). The code checks the requested url and it either responds with a 301 redirect or alternatively it tells CloudFront to fetch the appropriate index.html file from the S3 bucket.
And how much will this cost?
Unfortunately there's no free tier for Lambda@Edge functions, however pricing is low. CloudFront only executes the Lambda when it needs to fetch a file from S3 and due to caching that won't happen very often. So costs for using this Lambda should be negligable (if anything).
OK Lambda created. What's next?
If you're following the Gatsby AWS guide then the next step is to setup the Continuous Deployment environment using GitHub Actions.
Let's move on to Part 3: Continuous Deployment with GitHub Actions.