Background
It was just another day when I got an email with the following:
[Action Required] AWS Lambda Node.js 20.x end-of-life
We are contacting you as we have identified that your AWS Account currently has one or more AWS Lambda functions using the Node.js 20.x runtime.
We are ending support for Node.js 20.x in Lambda on April 30, 2026. This follows Node.js 20.x End-Of-Life (EOL) scheduled on April 30, 2026. End of support does not impact function execution. Your functions will continue to run. However, they will be running on an unsupported runtime which is no longer maintained or patched by the AWS Lambda team.
As described in the Lambda runtime support policy, end of support for language runtimes in Lambda happens in several stages.
- Starting on April 30, 2026, Lambda will no longer apply security patches and other updates to the Node.js 20.x runtime used by Lambda functions, and functions using Node.js 20.x will no longer be eligible for technical support. Also, Node.js 20.x will no longer be available in the AWS Console, although you can still create and update functions using Node.js 20.x via AWS CloudFormation, the AWS CLI, AWS Serverless Application Model (SAM), or other tools.
- Starting June 1, 2026, you will no longer be able to create new Lambda functions using the Node.js 20.x runtime.
- Starting July 1, 2026, you will no longer be able to update existing functions using the Node.js 20.x runtime.
We recommend that you upgrade your existing Node.js 20.x functions to the latest available Node.js runtime in Lambda before April 30, 2026.
Your impacted Lambda functions using the Node.js 20.x runtime are listed on the ‘Affected resources’ tab of your AWS Health Dashboard.
This notification is generated for functions using the Node.js 20.x runtime for the $LATEST function version.
So, I decided to check on the functions mentioned in the email and saw these notifications:
A new runtime is available for your function’s language: Node.js 24.x
The Node.js 20.x runtime is no longer supported. We recommend that you migrate your functions that use Node.js 20.x to a newer runtime as soon as possible.
To summarize the email and warnings, AWS Lambda is deprecating the Node.js 20 runtime in favor of Node.js 24. What neither mention is that doing so creates a problem: Node.js 24 drops support for callbacks in favor of the Async/Await feature introduced in ES2017.
Note
I defer to the Node.js documentation for a more detailed explanation of JavaScript asynchronous programming and callbacks.
There aren’t many resources on converting callback functions in Lambda@Edge, so this Techbit demonstrates how to fill that gap.
What’s in an AWS Lambda?
I haven’t mentioned AWS Lambda or Lambda@Edge yet, but they’ll be part of my S3 Web Host series.
The Amazon CloudFront documentation has more information about Lambda@Edge. As noted there, it’s an extension of AWS Lambda that runs functions at Amazon CloudFront using a specified runtime. Therefore, Lambda@Edge supports the same runtimes as AWS Lambda, which no longer supports Node.js 20, causing the email alert.
Why bother with Lambda@Edge? It turns out that when you use Amazon S3 as a static web host, you defer a lot of things done automatically by a beefier web server (like Apache or NGINX) for other AWS services to handle. Amazon S3 supports some features, but not others. To fill those gaps, you must use other services like Amazon CloudFront, which have their own gaps. Yep, it’s gaps all the way down.
What’s the benefit? By relying on AWS services to emulate an actual web server, you reduce the cost of running an actual web server in a different AWS service, like Amazon EC2. Running a web server on a used computer will always be the least expensive way to host a website. However, if you have web traffic from the other side of the world, you’ll start needing something like Amazon CloudFront to cache static pages around the world to improve load times.
Navigating AWS services is complicated, so follow my S3 Web Host series for guidance on getting around, including how to set up Lambda@Edge (in a future post).
Back to the problem at hand, using Lambda@Edge with a deprecated Node.js runtime is how we got into this situation, but how do we fix it?
Lambda@Edge Functions
As is often the case, the Amazon CloudFront documentation provides example functions, but most of them still use callbacks at the time of this writing, like this one that shows how to generate an HTTP redirect:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 'use strict'; exports.handler = (event, context, callback) => { /* * Generate HTTP redirect response with 302 status code and Location header. */ const response = { status: '302', statusDescription: 'Found', headers: { location: [{ key: 'Location', value: 'https://docs.aws.amazon.com/lambda/latest/dg/lambda-edge.html', }], }, }; callback(null, response); }; |
Even if you know how to convert the callback function itself to use Async/Await, you’d still need to know how to call the event handler in AWS Lambda using Async/Await.
Well, now what? If we don’t fix these functions, it’ll stop serving the S3 Web Host static files from Amazon CloudFront correctly.
Refactor Without Callbacks
There is one example using Async/Await that we can compare with and use to fix the old one:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | export const handler = async (event) => { const response = event.Records[0].cf.response; const headers = response.headers; const headerNameSrc = 'X-Amz-Meta-Last-Modified'; const headerNameDst = 'Last-Modified'; if (headers[headerNameSrc.toLowerCase()]) { headers[headerNameDst.toLowerCase()] = [{ key: headerNameDst, value: headers[headerNameSrc.toLowerCase()][0].value, }]; console.log(`Response header "${headerNameDst}" was set to ` + `"${headers[headerNameDst.toLowerCase()][0].value}"`); } return response; }; |
The above example shows how to change a response header based on another header, which is overkill for rewriting the first example.
Regardless, Line 1 initially calls the AWS Lambda event handler, and Line 17 returns the response. Note that the first example only generates an HTTP redirect. As a result, we don’t care about the original response because we generate our own. From there, we only need to return it.
Keeping the above in mind, this is all that’s needed to rewrite the first example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | export const handler = async (event) => { /* * Generate HTTP redirect response with 302 status code and Location header. */ const response = { status: '302', statusDescription: 'Found', headers: { location: [{ key: 'Location', value: 'https://docs.aws.amazon.com/lambda/latest/dg/lambda-edge.html', }], }, }; return response; }; |
Yep, all we did was rewrite the first 3 lines to use the async event handler, then rewrite the callback to return the response directly.
Just goes to show that things are easier with examples, which is why I advocate for (and provide) them.
Conclusions
Lambda@Edge is yet another powerful tool in the AWS toolbox that empowers us to completely replace a web server by building it in The Cloud. While AWS handles maintaining their tools, we still must manage what we make with them.
In this case, it’s an asynchronous event handler written in Node.js and running in Lambda@Edge on Amazon CloudFront to serve a static website worldwide.