I Tried Out Node.js 24 on Lambda@Edge
This page has been translated by machine translation. View original
On November 25, 2025, AWS Lambda started supporting Node.js 24. I covered how it behaves with regular Lambda functions in the article below, so this time I tested its behavior with Lambda@Edge.
Lambda@Edge is a Lambda function that runs at CloudFront edge locations, and it has different constraints from regular Lambda.
Node.js 24 includes a breaking change: the removal of the callback style. Through hands-on testing on real infrastructure, I confirmed that this change does not break compatibility with the basic features of Lambda@Edge (the four event hooks and header manipulation).
Test Environment
- Region: us-east-1
- Runtime: nodejs24.x
- Deployment method: CloudFormation
- Test items: All four event types (viewer-request, origin-request, origin-response, viewer-response)
Implementing the Lambda@Edge Functions
I prepared Lambda@Edge functions for the four triggers.
1. Viewer Request Handler
Viewer Request runs immediately after CloudFront receives a request from the viewer. You can add request headers and rewrite the URI.
exports.handler = async (event) => {
const request = event.Records[0].cf.request;
// Add a custom header
request.headers['x-custom-header'] = [{
key: 'X-Custom-Header',
value: 'Node24-Edge'
}];
// Example of URI rewriting
if (request.uri === '/old-path') {
request.uri = '/new-path';
}
return request;
};
Constraints:
- Timeout: 5 seconds
- Memory: 128MB
2. Origin Request Handler
Origin Request runs just before CloudFront forwards the request to the origin. You can read headers added by CloudFront (such as geographic information).
exports.handler = async (event) => {
const request = event.Records[0].cf.request;
// Add a header to the origin
request.headers['x-origin-header'] = [{
key: 'X-Origin-Header',
value: 'from-edge'
}];
// Read CloudFront headers
console.log('CloudFront-Viewer-Country:',
request.headers['cloudfront-viewer-country']);
return request;
};
Constraints:
- Timeout: 30 seconds
- Memory: 128MB
3. Origin Response Handler
Origin Response runs immediately after CloudFront receives the response from the origin. It is used for things such as adding security headers.
exports.handler = async (event) => {
const response = event.Records[0].cf.response;
// Add security headers
response.headers['strict-transport-security'] = [{
key: 'Strict-Transport-Security',
value: 'max-age=63072000'
}];
response.headers['x-content-type-options'] = [{
key: 'X-Content-Type-Options',
value: 'nosniff'
}];
return response;
};
Constraints:
- Timeout: 30 seconds
- Memory: 128MB
4. Viewer Response Handler
Viewer Response runs just before CloudFront returns the response to the viewer. It is used for things such as adding cache control headers.
exports.handler = async (event) => {
const response = event.Records[0].cf.response;
// Add cache control header
response.headers['cache-control'] = [{
key: 'Cache-Control',
value: 'public, max-age=3600'
}];
// Note: The status code cannot be changed in Viewer Response
return response;
};
Constraints:
- Timeout: 5 seconds
- Memory: 128MB
- Status code cannot be changed
CloudFormation Template
I prepared a CloudFormation template that deploys the four Lambda@Edge functions and a CloudFront distribution all at once.
Full template
AWSTemplateFormatVersion: '2010-09-09'
Description: Lambda@Edge with Node.js 24 - All Event Types
Resources:
EdgeFunctionRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
- edgelambda.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
ViewerRequestFunction:
Type: AWS::Lambda::Function
Properties:
FunctionName: node24-edge-viewer-request
Runtime: nodejs24.x
Handler: index.handler
Role: !GetAtt EdgeFunctionRole.Arn
Timeout: 5
MemorySize: 128
Code:
ZipFile: |
exports.handler = async (event) => {
const request = event.Records[0].cf.request;
request.headers['x-custom-header'] = [{ key: 'X-Custom-Header', value: 'Node24-Edge' }];
if (request.uri === '/old-path') {
request.uri = '/new-path';
}
return request;
};
ViewerRequestVersion:
Type: AWS::Lambda::Version
Properties:
FunctionName: !Ref ViewerRequestFunction
OriginRequestFunction:
Type: AWS::Lambda::Function
Properties:
FunctionName: node24-edge-origin-request
Runtime: nodejs24.x
Handler: index.handler
Role: !GetAtt EdgeFunctionRole.Arn
Timeout: 30
MemorySize: 128
Code:
ZipFile: |
exports.handler = async (event) => {
const request = event.Records[0].cf.request;
request.headers['x-origin-header'] = [{ key: 'X-Origin-Header', value: 'from-edge' }];
console.log('CloudFront-Viewer-Country:', request.headers['cloudfront-viewer-country']);
return request;
};
OriginRequestVersion:
Type: AWS::Lambda::Version
Properties:
FunctionName: !Ref OriginRequestFunction
OriginResponseFunction:
Type: AWS::Lambda::Function
Properties:
FunctionName: node24-edge-origin-response
Runtime: nodejs24.x
Handler: index.handler
Role: !GetAtt EdgeFunctionRole.Arn
Timeout: 30
MemorySize: 128
Code:
ZipFile: |
exports.handler = async (event) => {
const response = event.Records[0].cf.response;
response.headers['strict-transport-security'] = [{
key: 'Strict-Transport-Security',
value: 'max-age=63072000'
}];
response.headers['x-content-type-options'] = [{
key: 'X-Content-Type-Options',
value: 'nosniff'
}];
return response;
};
OriginResponseVersion:
Type: AWS::Lambda::Version
Properties:
FunctionName: !Ref OriginResponseFunction
ViewerResponseFunction:
Type: AWS::Lambda::Function
Properties:
FunctionName: node24-edge-viewer-response
Runtime: nodejs24.x
Handler: index.handler
Role: !GetAtt EdgeFunctionRole.Arn
Timeout: 5
MemorySize: 128
Code:
ZipFile: |
exports.handler = async (event) => {
const response = event.Records[0].cf.response;
response.headers['cache-control'] = [{
key: 'Cache-Control',
value: 'public, max-age=3600'
}];
return response;
};
ViewerResponseVersion:
Type: AWS::Lambda::Version
Properties:
FunctionName: !Ref ViewerResponseFunction
OriginBucket:
Type: AWS::S3::Bucket
Properties:
# ※注意: 123456789012 はダミーのAWSアカウントIDです。自身の環境に合わせて変更してください。
BucketName: !Sub 'node24-edge-origin-${AWS::AccountId}'
OriginAccessControl:
Type: AWS::CloudFront::OriginAccessControl
Properties:
OriginAccessControlConfig:
Name: node24-edge-oac
OriginAccessControlOriginType: s3
SigningBehavior: always
SigningProtocol: sigv4
CloudFrontDistribution:
Type: AWS::CloudFront::Distribution
Properties:
DistributionConfig:
Enabled: true
DefaultCacheBehavior:
TargetOriginId: S3Origin
ViewerProtocolPolicy: redirect-to-https
CachePolicyId: 658327ea-f89d-4fab-a63d-7e88639e58f6
LambdaFunctionAssociations:
- EventType: viewer-request
LambdaFunctionARN: !Ref ViewerRequestVersion
- EventType: origin-request
LambdaFunctionARN: !Ref OriginRequestVersion
- EventType: origin-response
LambdaFunctionARN: !Ref OriginResponseVersion
- EventType: viewer-response
LambdaFunctionARN: !Ref ViewerResponseVersion
Origins:
- Id: S3Origin
DomainName: !GetAtt OriginBucket.RegionalDomainName
OriginAccessControlId: !Ref OriginAccessControl
OriginShield:
Enabled: true
OriginShieldRegion: us-east-1
S3OriginConfig: {}
BucketPolicy:
Type: AWS::S3::BucketPolicy
Properties:
Bucket: !Ref OriginBucket
PolicyDocument:
Statement:
- Effect: Allow
Principal:
Service: cloudfront.amazonaws.com
Action: s3:GetObject
Resource: !Sub '${OriginBucket.Arn}/*'
Condition:
StringEquals:
AWS:SourceArn: !Sub 'arn:aws:cloudfront::${AWS::AccountId}:distribution/${CloudFrontDistribution}'
Outputs:
DistributionDomainName:
Value: !GetAtt CloudFrontDistribution.DomainName
ViewerRequestVersion:
Value: !Ref ViewerRequestVersion
OriginRequestVersion:
Value: !Ref OriginRequestVersion
OriginResponseVersion:
Value: !Ref OriginResponseVersion
ViewerResponseVersion:
Value: !Ref ViewerResponseVersion
OriginBucket:
Value: !Ref OriginBucket
Why Origin Shield was adopted:
Because Lambda@Edge runs at each edge location, logs are scattered across regions around the world. By enabling Origin Shield, the logs for Origin Request/Response events can be consolidated in us-east-1, which makes checking and analyzing logs easier.
Note on CloudFormation ZipFile:
When using the ZipFile property of CloudFormation, you cannot include package.json, so CommonJS format (exports.handler) is required. If you want to use ES Modules format (export const handler), you need to create a separate ZIP file, upload it to S3, and use Code.S3Bucket / Code.S3Key.
After deployment, I confirmed in the Behavior settings of the created CloudFront distribution that Lambda@Edge was associated.

Deployment Steps
I created the CloudFormation stack using the CLI.
aws cloudformation create-stack \
--stack-name node24-lambda-edge \
--template-body file://cloudformation-edge.yaml \
--capabilities CAPABILITY_IAM \
--region us-east-1
Verification
1. Uploading the Test File
# Create an HTML file for testing
cat > test.html << 'EOF'
<!DOCTYPE html>
<html>
<head>
<title>Lambda@Edge Node.js 24 Test</title>
</head>
<body>
<h1>Lambda@Edge Node.js 24 Test Page</h1>
<p>This page tests all four Lambda@Edge event types.</p>
</body>
</html>
EOF
# Upload to S3
# Note: Replace node24-edge-origin-123456789012 with the bucket name created in your own environment
aws s3 cp test.html s3://node24-edge-origin-123456789012/test.html --region us-east-1
2. Accessing via CloudFront
curl -I https://d1234567890abc.cloudfront.net/test.html
3. Results
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 451
Date: Fri, 28 Nov 2025 11:39:40 GMT
Last-Modified: Fri, 28 Nov 2025 11:39:30 GMT
ETag: "b4ec68bdc1ddae9aa15ce5803a10c28f"
Strict-Transport-Security: max-age=63072000
X-Content-Type-Options: nosniff
Cache-Control: public, max-age=3600
Via: 1.1 7bb66c5fc1e732675b1f05b324f80096.cloudfront.net (CloudFront)
X-Cache: Miss from cloudfront
I was able to confirm the following behavior of the Lambda@Edge functions.
| Event type | Added header | Value |
|---|---|---|
| Origin Response | Strict-Transport-Security | max-age=63072000 |
| Origin Response | X-Content-Type-Options | nosniff |
| Viewer Response | Cache-Control | public, max-age=3600 |
| Viewer Request | X-Custom-Header | Node24-Edge (*) |
| Origin Request | X-Origin-Header | from-edge (*) |
* Request headers are not shown in the response, so I verified them in CloudWatch Logs
Summary
Through this testing, I confirmed that the Node.js 24 runtime works properly in a Lambda@Edge environment and that the basic features are usable.
Node.js 24 is scheduled to receive long-term support (LTS) until April 2028, and its execution performance at edge locations has also improved. I recommend making it your first choice for new development going forward.
If your existing Lambda@Edge functions use the callback style, migrating will require rewriting them with async/await. However, Lambda@Edge functions generally tend to have small amounts of code, so the cost of the modification is likely to be relatively low.
That said, updating right before EoL carries unexpected risks. Please consider planning your migration to Node.js 24 with plenty of time to spare.

