The purpose of this guide is to walk you through the step-by-step process of creating 301 redirects for files hosted in Amazon S3 using Lambda@Edge functions.
For more information on the differences between CloudFront functions and Lambda@Edge Functions, please visit this link.
When dealing with multiple folders within the bucket, it’s essential to specify the files and folders for which you intend to implement redirects. Otherwise, the code will be applied to every file request. Additionally, ensure that the asset exists in the new route.
Create the function #
- Go to your AWS dashboard and search for Lambda. Once there, on the top right corner click on
Create Function
button - For our case study, we are going to choose Node.js, but you have other options like .NET, Python, Java, Ruby
- Leave the rest as it is and click
Create Function
.
This base code will stop serving the final asset and will instead return an OK screen.
Handling Requests #
The function receives an event, and within this event lies the request data. The provided code offers 3 samples on how to handle the requests:
- If the request URI contains
/assets/old-slug/
redirect to/assets/new-slug/
- If the request URI contains one of the URLs provided in a JSON variable, redirect to another asset.
- If the request URI ends with a known string, redirect by replacing the ending of the asset’s name.
Redirecting an asset is not limited to these 3 samples, you have the flexibility to redirect to alternative buckets or domains, modify asset header responses, implement cache key normalization, handle HTTP to HTTPS requests, and explore various other customization options. Given that the code is written in pure JavaScript, you have the freedom to use innovative approaches to achieve your desired solutions.
import { createRequire } from 'node:module';
const require = createRequire(import.meta.url);
const brokenUrlsObj = require('./routes.json');
export const handler = async (event) => {
const domain = 'https://myS3bucketDomain.com';
const currentFolderUri = '/assets/old-slug/';
const newFolderUri = '/assets/new-slug/';
const request = event.Records[0].cf.request;
var newUri = request.uri;
console.log(request)
var brokenUrls = brokenUrlsObj[0];
if(request.uri.includes(currentFolderUri)){
//change the uri
newUri = request.uri.replace(currentFolderUri, newFolderUri)
return {
status: '301',
statusDescription: 'Moved Permanently',
headers: {
location: [{ value: domain + newUri }]
}
};
}
//fully change a filename
if (brokenUrls[request.uri] !== undefined) {
return {
status: '301',
statusDescription: 'Moved Permanently',
headers: {
location: [{ value: domain + brokenUrls[request.uri] }]
}
};
}
//change part of the filename
else if (request.uri.endsWith('-categoryA.pdf')) {
newUri = request.uri.replace('-categoryA.pdf', '-categoryB.pdf')
return {
status: '301',
statusDescription: 'Moved Permanently',
headers: {
location: [{ value: domain + newUri }]
}
};
}
return request;
};
From lines 15 to 27, it redirects any request that contains a URI with /assets/old-slug/
to /assets/new-slug/
From lines 30 to 38, it redirects any request that matches any URI in the JSON file, brokenUrls
, that was imported at the beginning of code, to its new URL.
From lines 40 to 50, it redirects any request that ends with -categoryA.pdf
to a new URL for it, ending with -categoryB.pdf
.
Lastly, line 52 return request;
will allow all the other requests to keep going with their normal route.
const request = event.Records[0].cf.request;
The testing JSON file for this function is:
[
{
"/assets/folder1/yourFilename1.pdf":"/assets/new-slug/yourFilename1.pdf",
"/assets/folder1/yourFilename2.pdf":"/assets/new-slug/yourFilename2.pdf",
"/assets/folder1/yourFilename3.pdf":"/assets/new-slug/yourFilename3.pdf",
"/assets/folder1/yourFilename4.pdf":"/assets/new-slug/yourFilename4.pdf",
"/assets/temp-folder/yourFilenameA.pdf":"/assets/new-slug/yourFilenameA.pdf",
"/assets/temp-folder/yourFilenameB.pdf":"/assets/new-slug/yourFilenameB.pdf",
"/assets/temp-folder/yourFilenameC.pdf":"/assets/new-slug/yourFilenameC.pdf",
"/assets/temp-folder/yourFilenameD.pdf":"/assets/new-slug/yourFilenameD.pdf"
}
]
How to test your code. #
Since the language is Javascript, you can use console.log()
to print and debug any variable.
For example: consoel.log(request);
Once you have your code in place, click on Deploy
, which is the equivalent to Save (don’t worry, is not going to put the function live just yet), then click on the blue button Test
. (If you don’t hit Deploy
, you won’t see the changes on your tests).
For Test Event Action
, leave it as Create new event
, write a name for your test, and then select CloudFront A/B Test
under the Template
select, it will give you a default template to simulate a request.
On the Event JSON
find URI
and write one of your possible cases on the field URL Path
, for example /assets/old-slug/testing.pdf
, and click Save
. This test will be reusable. With Lambda@Edge, you can create and save different tests with different scenarios.
After saving the test, it will take you back to your main code, click the test button and you will see the response to your test request.
A new tab named Execution Result
will contain the obtained response, in this case, it was a redirect from
myS3bucketDomain.com/assets/old-slug/yourFilename1.pdf
to
myS3bucketDomain.com/assets/new-slug/yourFilename1.pdf
.
If your code includes a console.log()
statement, the corresponding output will be visible under Function Logs
next to the word INFO, the request variable in my case. (See last image for reference).
IAM permissions #
“To configure Lambda@Edge, you must set up specific IAM permissions and an IAM execution role. Lambda@Edge also creates service-linked roles to replicate Lambda functions to CloudFront Regions and to enable CloudWatch to use CloudFront log files.”
Steps:
- While in the function, click the tab
Configuration
- On the right Sidebar click
Permissions
- Click the role name display under
Role name
, this is the role for your function and it will take you to the Identity and Access Management (IAM)
- While in the IAM Dashboard, click the tab
Trust relationships
- Click
Edit trust policy
- Add the line
"edgelambda.amazonaws.com"
under ‘“Service”’, - Click
Update Policy
.
Your code will look like this:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": [
"edgelambda.amazonaws.com",
"lambda.amazonaws.com"
]
},
"Action": "sts:AssumeRole"
}
]
}
Create a Behavior for your CloudFront distribution #
To be able to execute the function, you need to create a behavior on your CloudFront distribution. This will trigger the function under certain circumstances.
For our case scenario, we are going to create a behavior that it will trigger the function if the request URI ends with the extension .pdf
, and when there is a Viewer Request
.
Once we publish a version of our new function, we will put the Function ARN
string that we get from the function dashboard, on the field Function ARN / Name
on the Behavior of our CloudFront distribution.
Publishing the Function #
Once you are confident with your code, have configured the IAM Role and Permission, have set up the behavior, and have done tests for different possible requests, it’s time to publish your function.
Option 1: #
Under Lambda -> Functions -> YourFunctionName, click the Action
button on the right top corner of the screen. Then, there is two ways to do it:
- Select
Publish new version
, write some details description for thisversion
and clickPublish
- Copy the value under
Function ARN
, that’s the value you will have to paste back in the CloudFront behavior you created, on the fieldFunction ARN / Name
and save.
Now your function is live.
Option 2: #
Under Lambda -> Functions -> YourFunctionName, click the Action
button on the right top corner of the screen. Then, there is two ways to do it:
- Select
Deploy to Lambda@Edge
- Select your distribution ID (you might need to open a new tab with your CloudFront distributions to copy the distribution ID) needed
- Write down your behaviour,
for this case/*.pdf
- For
CloudFront event
selectViewer Request
- Check
Confirm deploy to Lambda@Edge
- Click
Deploy
This automatically will update the Viewer Request
under your behavior dashboar to use the new version.
Now your function is live.
Edit your function #
Notice how under your function dashboard, there is a new banner that says:
To be able to edit this function, you need to:
- Click the button
Update code
. - Update your code
- Deploy
- Test
- Publish Version
- Update
Function ARN / Name
on the CloudFront behavior you created to have the new version. - Repeat as much as you need.
Why did we end up using Lambda@Edge? #
This client had about 2000 unique redirects to be validated. These URLs would have to be defined on the variable brokenUrls
of our code, due to CloudFront Functions size limitations, this variable made the file too big for the available scope.
If you have any questions, feel free to contact me.