I Tried Out Node.js 24 on Lambda@Edge

I Tried Out Node.js 24 on Lambda@Edge

I tried the Node.js 24 runtime, supported since November 2025, on Lambda@Edge. I built the environment with CloudFormation and confirmed it works on all Viewer/Origin Request/Response events.
2025.11.28

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.

https://dev.classmethod.jp/articles/aws-lambda-node-js-24/

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.

The four triggers
Resizing images with Amazon CloudFront & Lambda@Edge

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.

CloudFront_Behavior settings

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.


AWS re:Invent 2025まとめ情報

クラスメソッドは2025/12/1〜5にラスベガスで開催されたAWSカンファレンスイベント「re:Invent」を今年も特集しました。ぜひ一度ご覧ください。 re:Invent 2025ポータルサイトを見るAWS re:Inventはこちら

Share this article

AWSのお困り事はクラスメソッドへ