Edge Lambda functions that support serving of an SPA via CloudFront using cursor files.
The source code of the lambdas lives in the src
directory. Each edge lambda's index.js
file
exposes a handler
method which is the entrypoint used in CF configuration.
Tests are collocated with the source files, one test suite per lambda. They are using
Jest. Run tests with make test
.
Each lambda is built using Vercel's ncc
which takes care of
bundling and compiling. Run build of all lambdas with make build
Since Lambda@Edge functions
can't use environment variables,
we use JSON config files (config.json
) uploaded next to the lambda index.js
file to store
configuration. The config files can be e.g. generated by Terraform as part of the Lambdas deployment
process. Allowed configuration options:
Name | Description | Type | Default | Required |
---|---|---|---|---|
environment | AWS environment. Feature branch previews are only enabled on staging. We also add X-Robots-Tag which blocks indexing in staging |
production,staging |
n/a | yes |
originBucketName | Name of the S3 bucket which stores the cursor files | string |
n/a | yes |
originBucketRegion | AWS region where the above bucket lives (e.g. eu-west-1 ) at |
string |
n/a | yes |
previewDeploymentPostfix | The base part of the app url, e.g. app.staging.pleo.io . Only applicable in staging. |
string |
n/a | yes |
defaultBranchName | The name of the default branch of the repo that deploys the app | string |
master |
no |
blockIframes | Should the X-Frame-Options custom header be added to block rendering of the app in iframes? |
bool |
false |
no |
Lambda@Edge lambdas are used when serving assets from Cloudfront (CDN) distributions. They are triggered in the request/response cycle of a CDN-backed asset at one of the four stages (viewer request, origin request, origin response and origin request). They are invoked with either
CloudFrontRequestEvent
orCloudFrontResponseEvent
event depending on the stage the are associated with.
In this setup the app uses edge lambdas triggered on viewer request and viewer response events. This means that those lambdas will run on every request that is handled by the default distribution behavior which they are associated with.
-
Viewer Request - triggered before the request is sent to the origin (in our case S3 bucket). This lambda can modify which asset is requested from the origin. In our case:
-
Inspect the Host header of the request and determine which version of the code the user wants to see.
For example, a request to
app.staging.example.com
is for the main branch of the app, while a request tomy-branch.app.staging.example.com
is a request for a feature branch (my-branch
) version of the app, and a request topreview-{version}.app.staging.example.com
is a request for a specific version. -
Based on the above, fetch the cursor file (containing the current active app version) from the S3 origin bucket to figure out which HTML file to request from CDN. The cursor file is updated as part of the CD pipeline.
For example, for the main branch requested we would fetch
deploys/main
file from S3, and for a feature branchdeploys/my-branch
. Then we read its contents, which would be some SHA hash (like e.g.ce4a66492551f1cd2fad5296ee94b8ea2667eac3
).Note that this is a call to an external data source, and although who chose to store this file in the same bucket as the files served by this CDN distribution, in principle it could be any other data source (API, DynamoDB, etc.). To mitigate the potential performance penalty from making that request (since it's a non-cacheable, blocking request to a resource in a non-edge location) we use a long lived HTTP connection and cache it in global lambda scope which makes it available for consecutive invocations. For the whole discussion of this topic please see AWS's guide to using external data in Edge Lambda.
-
Once the app version is established, the request object is modified to fetch the right version of the file from the origin bucket.
For example, we might modify the request to fetch
/html/ce4a66492551f1cd2fad5296ee94b8ea2667eac3/index.html
following the example above.Note that this request is now for an asset with a unique name, that can be fully cached on the CDN level. We upload the file to S3 with
max-age=31536000,immutable
cache control settings, and in the viewer response lambda we adjust the cache headers when sending the response to the browser tomax-age=0,no-cache,no-store,must-revalidate
so that the browser doesn't cache the non-unique url locally. This mitigates the latency added by the cursor file request further.
-
-
Viewer Response - triggered just before the response is returned to the user's browser. Currently we use it set translation-related headers on the response
Well known files under /.well-known
URI are
exposing some information under a consistent URL across all websites (e.g.
.well-known/apple-app-site-association
). These are handled in a special way by this module, and
should work as expected if placed in the .well-known
directory (and not in /static
) when
uploaded to S3.
Support for separately served translations can be achieved using the isLocalised
config option.
When enabled, the viewer request lambda will fetch the latest version of the translations from S3
(from a cursor file assumed present at translation-deploy/latest
key in the origin bucket), and
pass it to the viewer response lambda which exposes it via a cookie on the response. For more
details see the addons/translations.ts
file.
These lambdas as used internally by the Terraform module in this repo. Refer to documentation of the module for more information.