thoughts on software development and everything else

Pretty URLs with Hugo, S3 and Lambda


By default, Hugo uses pretty URLs: your page addresses look like https://ronniegane.kiwi/blog/ rather than /blog/index.html or /blog.html .

The directory structure Hugo creates for this has each page exisiting as an index.html file inside of a directory, and then using the directory name as the page address.

To make these work with S3 we need to set up a Lambda function to rewrite the addresses to map to the actual files in s3: /2018/11/16/pretty/ -> /2018/11/16/pretty/index.html

We also need to account for people potentially leaving off the trailing slash when they type an address, so that /2018/11/16/pretty will still work. Originally my Lambda function didn’t do that, which led to some bugs where I forgot to add a slash to my own links. Hugo’s relURL function doesn’t add the closing slash for you.

Lastly, we don’t want to rewrite links to resources like CSS or images. So we can’t just add /index.html to all requests, otherwise we will try to fetch /css/main.css/index.html

The Lambda function I use is mostly cribbed from this Amazon article: Implementing Default Directory Indexes in Amazon S3-backed Amazon CloudFront Origins Using Lambda@Edge

'use strict';
exports.handler = (event, context, callback) => {
    // Extract the request from the CloudFront event that is sent to Lambda@Edge 
    var request = event.Records[0].cf.request;

    // Extract the URI from the request
    var olduri = request.uri;

    // Append /index.html to any request (excluding <file>.<ext> pattern)
    var newuri = olduri.replace(/(\/[^.\/]+)\/?$/, '$1\/index.html');
    // Log the URI as received by CloudFront and the new URI to be used to fetch from origin
    console.log("Old URI: " + olduri);
    console.log("New URI: " + newuri);
    // Replace the received URI with the URI that includes the index page
    request.uri = newuri;
    // Return to CloudFront
    return callback(null, request);


With this in place, we successfully redirect to the index.html files for each page. https://ronniegane.kiwi/blog and https://ronniegane.kiwi/blog/ both work, while https://ronniegane.kiwi/css/main.css avoids redirection.