The ByteLab logo

Serve an S3 static website with clean URLs using CloudFront and Lambda@Edge

Posted on 2 August 2021

If you're using a static site generator, you may encounter issues when serving your S3 static site in combination with CloudFront (access denied, 404s where you don't expect them, etc.). An example of what we were seeing on this very site, when a URL didn't end in "/" (denoting that it was a directory) S3 would return a 403 to Cloudfront:

Returns 403:
/post/2021-07-31/1-how-to-deploy-to-multiple-aws-regions-in-laravel-vapor

Returns expected content (200):
/post/2021-07-31/1-how-to-deploy-to-multiple-aws-regions-in-laravel-vapor/

We fixed this by utilising Lambda@Edge, a feature of CloudFront that allows us to modify the behaviour of CloudFront.

The fix

In the AWS console, navigate to Cloudfront and choose "Functions" from the sidebar menu.

Cloudfront sidebar menu with arrow pointing to functions item

Click "Create function". Give the function a name (e.g. static-website) and click "Create function" again.

Replace the code in the "Development" tab with the following:

function handler(event) {
    var request = event.request;

    if (request.uri.match(/\.[0-9a-z]+$/i)) {
        // The uri is for a file e.g. *.png, *.css, etc.
        return request;
    }
    
    if (request.uri.endsWith('/')) {
        // The uri ends in '/' - add 'index.html'.
        request.uri = `\${request.uri}index.html`;
    } else {
        // The uri ends doesn't end with '/' - add '/index.html'
        request.uri = `\${request.uri}/index.html`;
    }
    
    return request;
}

Replacing code in the development tab

Don't forget to save your changes! Click "Save changes".

Switch to the "Publish" tab and click "Publish function". You should now see a new area below the button just clicked called "Associated distributions".

associated distributions section

Click "Add association". A modal popup should appear, in the fields that appear enter the following:

  • Distribution - the distribution you want to apply the clean URLs function to.
  • Event type - this should be: "Viewer request".
  • Cache behavior - set this as the cache behaviour that handles your static content (index.html, /products/index.html, etc.), if you have a simple set up this will most likely be "Default (*)".

adding an associated distribution

Complete the set up by clicking "Add association". This should take effect quite quickly, around 5 minutes at the most.