Static websites are great, but...
At Si Novi we love the performance and security of static websites hosted on AWS S3 and CloudFront. However, unlike the working mechanisms of more traditional Linux-based Apache web servers, static website infrastructure deployed with S3 and CloudFront doesn't supply some some key features by default - such as automatic redirection of trailing slashes in a URL. This can cause confusion for users when they unexpectedly run into missing pages and 404 errors, and can have a negative impact on your website SEO.
We created a serverless solution using Node.js and AWS Lambda@Edge, hosted within the AWS Serverless Repository, which we now use to optimise the URL redirection on all our static website deployments.
serverless solution
Using Node.js and Lambda@Edge to optimise static website delivery
We realised that the best way to solve the trailing slash redirection issue on S3 & CloudFront websites was by using Lambda@Edge - AWS's serverless Lambda service, running at edge locations around the globe.
A simple Node.js script, deployed as a Lambda@Edge process, parses the incoming request from a user's browser. If it detects a trailing slash, it responds with HTTP 301 redirect to the same URL, minus the slash on the end. The script also preserves any query string parameters. The user's browser then makes the redirect to the non-slash URL which passes through the Lambda@Edge process untampered, and retrieves the data from CloudFront or S3 as normal.
Rather than writing the Node.js script, storing it privately in Github and deploying it manually to each of our static website accounts, we decided to use the AWS Serverless Application Respository, which is a library of useful serverless applications built by AWS and third-party software vendors. The AWS Serverless Repo allows anyone using AWS to deploy ready-made serverless applications into their own AWS infrastructure.
The Serverless Application Model
The AWS Serverless Repository uses AWS's own Serverless Application Model (SAM) format to package the applications and define their deployment settings. In the YAML SAM file for our serverless application we defined the Node runtime, handler and other runtime properties for our Lambda function, including the path to the source files within the package.
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: SAM configuration for creating a Lambda@Edge function to remove trailing slashes from CloudFront requests
Resources:
LambdaEdgeRemoveTrailingSlash:
Type: 'AWS::Serverless::Function'
Properties:
Handler: index.handler
Runtime: nodejs8.10
CodeUri: src/
Role: !GetAtt LambdaEdgeRemoveTrailingSlashRole.Arn
Description: 'A Lambda@Edge function to remove trailing slashes from CloudFront requests'
MemorySize: 128
Timeout: 3
AutoPublishAlias: live
...
Publishing to and deploying an application from the AWS Serverless Repository
As the author of a serverless app, you first publish your application to the AWS Serverless Repo using either the CLI or AWS Console (AWS Documentation here). Initially your app is private, and only available to the same AWS account, or other specific accounts you nominate. This is useful for testing the deployment and running of your application before releasing it publicly.
Once an application is public in the AWS Serverless Repository, it can be deployed into your AWS account by clicking the Deploy button in the repo, which will then launch the package in CloudFormation and build the application into your Lambda applications in the US-East-1 (N. Virginia) region. From there you can then deploy the Lambda function as a Lambda@Edge process and choose which CloudFront distribution you'd like to associate the function with.
A small gotcha in the AWS Serverless Repo is that functions that require IAM Role creation get sort of quarantined behind a checkbox which requires the user to deliberately Show apps that create custom IAM roles or resource policies. I mean fair enough - an app could be requesting all sorts of IAM permissions, however our app keeps it simple - only requesting standard Lambda function basic execution permissions and also setting up the Trust Relationship required to publish the Lambda function onto Lambda@Edge. It's a good idea to review the permissions of a serverless function to make sure you're happy with what it's provisioned in IAM.