Harnessing Edge Computing in AWS to Generate Static Site Previews

At T5, we are big advocates of applying a shift-left mentality to many different aspects of product delivery, from testing to security. In this blog post, I will give you an example of how you can validate early by using ephemeral preview environments during the development of a static website, such as a company blog or a brochure website

The benefits of preview environments in the development workflow

Hugo is a fast, simple static site generator that we use to build some of our static sites at T5. As with other static site generators, Hugo sites can be readily hosted on a lot of different web platforms, like Cloudflare Pages, Vercel or Amazon S3. A very interesting feature of hosting services like Cloudflare or Vercel is that they also host preview versions of your site when you make changes to a draft version of it. This is a powerful tool as it allows developers to:

  • share their changes on a public URL so that both non-technical and technical people can easily review and comment on them
  • shift quality assurance left, obtaining feedback early on in the development process, before the changes are published to the live site

This is particularly useful when making changes to the look and feel of a site, which are harder to visualise than if they were just some Markdown text for a new blog post.

At T5 Digital, we host most of our internal company infrastructure in AWS, so the simple S3 & CloudFront combination for cloud-based static site hosting is one we are very familiar with and use a lot. Hugo has built-in support to deploy to these AWS services, which fulfil most static site hosting needs. However, replicating the aforementioned hosted site preview functionality with these products is somewhat trickier, though entirely possible with a bit of edge compute magic. Let’s take a look!

The preview workflow

The workflow we are aiming to build is as follows:

  1. The author makes some changes to the static site on a branch in a source control system like GitHub or similar
  2. The author raises a pull request to share the new version of the site with others to get feedback before the changes are published
  3. The PR triggers a pipeline build to automatically deploy the changes to an isolated preview environment
  4. The author can then share a preview link for their peers to visit so they can see the changes before they’re published to the live site.

So, can we do this with S3 and CloudFront? Yes! Indeed, there are several ways to accomplish this.

One simple approach would be to set a unique URL prefix for every preview site, e.g. `engineering.t5.digital/david-preview`, and append new behaviours to the distribution to map these preview URL prefixes to different S3 origins. Better yet, one could select the same S3 origin but on a different root prefix. This could get unwieldy and slow though, as the CloudFront distribution needs to be updated every time a new preview site is deployed ­­—and CloudFront is very slow to update.

Alternatively, we could host as many preview sites as we want using the same bucket, CloudFront config and even domain name! The key ingredient to make this possible is to run edge functions on Lambda@Edge.

With this approach, all versions of the static site are deployed to the same S3 bucket. CloudFront then dynamically selects the correct preview version of the site based on specific properties of the inbound client request.

This sounds great! But wait, what is an edge function?

What is Lambda@Edge?

CloudFront Lambda@Edge is a form of edge computing on the AWS cloud that enables you to run Python or Node.js Lambda functions directly on the AWS CloudFront CDN. Edge functions provide lightweight processing capabilities and deliver responses with extremely low latency, as they run across worldwide distributed edge locations that are very close to end users. This makes them ideal for use cases such as transparent traffic redirection and manipulation as well as UX/UI experiments that don’t negatively impact the performance of your site.

AWS also launched CloudFront Functions in 2021. CloudFront Functions are a lighter flavour of CloudFront edge compute —very similar to CloudFlare Workers— with better performance than Lambda@Edge but more constraints in their capabilities and the type of requests they’re able to handle. This makes them suitable for a narrower set of use cases that are outside of the scope of this blog post.

Edge computing has been gaining a lot of traction lately, as more companies begin to ship their own edge compute products, enabling people to build immensely scalable and fast frontend experiences to their user base.

Managing preview environments in CloudFront with Lambda@Edge

We can host multiple, self-contained versions of a Hugo site under different root prefixes in the same S3 bucket. Lambda@Edge functions can then inspect the properties of incoming client requests (HTTP headers, parts of the URL, etc.) and select the appropriate CloudFront origin on the fly. In my example, I will be showing how to do this using HTTP headers, but you could inspect other properties of a request, such as the URL path, query strings, source IP address (A/B testing anyone?) and more.

In order for this to work, we want to ensure that

  • The S3 bucket the site is hosted on is fully public
  • The CloudFront distribution forwards the request header we need to the correct origin using an Origin Request policy (the `AllViewer` policy works well, as it forwards everything)
  • Caching is disabled while debugging. Then, enable it and ensure it caches the selector header so that cached responses factor in the user’s preview environment selection.

Ensure the origin the edge function returns is an `s3` origin, not a custom one. The origin you set up in the CloudFront distribution itself can be the default “no-header” version, although you could also just ignore it altogether, as the Lambda@Edge function can handle the CloudFront Origin selection on its own with no additional configuration.

Here is an example Node.js 18 implementation of this type of function:

				
					export const handler = (event, context, callback) => {
  const request = event.Records[0].cf.request;

  if (request.headers['x-preview-env']) {
    const theme = request.headers['x-preview-env'][0].value;

    // Lambda@Edge doesn't support Lambda environment variables so
    // we have to hardcode the preview bucket S3 domain name here.
    const s3DomainName = 'preview.engineering.t5.digital.s3.us-east-1.amazonaws.com';

    request.origin = {
      s3: {
        domainName: s3DomainName,
        region: 'us-east-1',
        authMethod: 'none',
        path: `/${theme}`,
        customHeaders: {}
      }};
    request.headers['host'] = [{ key: 'host', value: s3DomainName}];

    const olduri = request.uri;

    // Match any '/' that occurs at the end of a URI. Replace it with a default index
    const newuri = olduri.replace(/\/$/, '\/index.html');

    request.uri = newuri;
  }

  callback(null, request);
};
				
			

It is also a good idea to implement the logic to convert trailing forward slashes on request paths, so requests to `/` get converted to `/index.html`.

Clients can now navigate to the live blog URL and use browser extensions like Requestly to automatically append the preview environment switch header and obtain the preview version of the website.

Conclusion

I have shown how you can utilise the edge computing capabilities of the AWS CloudFront CDN to host preview versions of your static website that people can visit and give feedback, without impacting your live site. This will help you optimise development feedback loops, allowing you to validate changes to your static sites quickly and safely. Happy previewing!

To find out more about where to start, or how to bring the transformation magic sauce into your organisation

Related insights

Why Full-Stack Thinking Drives Better Solutions

Uncovering the Limitations of Amazon EC2 Burstable Instances

Why Focusing on Principles, Not Frameworks, Makes all the Difference

Let’s get started

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.

Let’s get started