There are several reasons why you might want to redirect users’ requests. One of the more obvious ones being for SEO, to only serve requests from e.g. apex (naked) domain and redirect request going to www to apex domain. Or the other way around. In this example we will focus on redirecting requests to subdomain www to apex domain. So any request to www.example.com should get redirected to example.com.

This is a dead simple task when we have a server running that we are in control of, e.g. NGINX or Apache can be easily configured to redirect those requests. But how to do this when we have a static page served by a CloudFront distribution?

The answer lies in functions. CloudFront enables us to run functions at different stages of the request, for example on “Viewer Request”, which we will be using to redirect our users depending on the domain. There are two types of functions that can help us achieve this (and more); Lambda@Edge and CloudFront functions.

Why CloudFront Functions and not Lambda@Edge?

Lambda@Edge is certainly up for the task. But it is a bit of an overkill. And it will end up costing us more. There are too many differences between the two to write about here, if you want to know more read this article by AWS.

The main point for this use-case is price. A million invocations of CloudFront Functions costs at the time of this writing $0.10, whereas a million requests of Lambda@Edge cost $0.60. And on top of that you pay for execution time. Plus you get 2 million invocations of CloudFront functions per month for free in “Always free tier”.

To be fair, all this will amount to peanuts for most websites, but as it does not give us any benefit in this case to go with Lambda@Edge, we might as well keep those peanuts.

How to set up redirects with CloudFront functions

We will assume that you already have a CloudFront distribution set up to receive requests from both www.example.com and example.com. If you do not, there a number of resources describing that, or even this CloudFormation template prepared by AWS.

We will also assume you know how to deploy a CloudFront function, if you don’t AWS have written a tutorial describing that.

So now that we have everything in place we can go ahead and add our custom function. It is really quite simple.

function handler(event) {
    var uri = event.request.uri;
    var host = event.request.headers.host.value;

    if (host === 'www.example.com') {
        return {
            statusCode: 301,
            statusDescription: 'Redirecting to Apex domain',
            headers: { 
                'location': { 'value': 'https://example.com' + uri } 
            }
        }
    }
}

All you need to do to adapt it to your needs is replace the references to example.com with your own domain. Depending on if you want to redirect www to non-www or the other way around you might also need to adapt the if statement and the location header we are returning.

All that is left to do is to associate this newly created function to our CloudFront distributions’ Viewer Request and voilà, we are redirecting.

Further optimisation

Here you might be thinking: “Wait a minute, this means that this function will be called every time someone makes a request to my website” and you would be correct. It does indeed sound somewhat wasteful. Can’t we do better? And the answer is yes, yes we can.

We can split our distributions. Instead of having one distribution serving both requests to www.example.com and example.com we can make one for each of those. The distribution does not cost anything in itself, it only costs per request it serves, so having two is no costlier than having one.

So then our function becomes even simpler. We will only enable it on the distribution that serves the domain we want to redirect, in our case www. So we can remove the conditional logic, as we will always redirect:

function handler(event) {
    var uri = event.request.uri;
    return {
        statusCode: 301,
        statusDescription: 'Redirecting to Apex domain',
        headers: { 
            'location': { 'value': 'https://example.com' + uri } 
        }
    }
}

I like making things cheaper and cleaner, even when the impact is minimal. I find it a good practice to always be mindful of costs and, consequently, environmental impact of the code I write.