jonelantha: Blog


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):

  1. 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 /)
  2. 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

  1. In the AWS console go to the Lambda section
  2. 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
  3. On the main Lambda dashboard, click Functions on the left and then click Create function (orange button)
  4. Select Author From Scratch
  5. In the Basic information section:
    • Function Name: Enter CloudFrontIndexHandler
    • Runtime: leave as Node.js 14.x
  6. 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 select Basic Lambda@Edge permissions (for CloudFront trigger) when you see it in the list
  7. Click Create Function at the bottom
  8. 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;
    }
  9. Select Save from the File menu
  10. Click the Actions button (a menu should appear)
  11. 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)
  12. 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

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

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.


© 2003-2023 jonelantha