I Tried Node.js 24 with Lambda@Edge

I Tried Node.js 24 with Lambda@Edge

I tried the Node.js 24 runtime with Lambda@Edge, which will be supported from November 2025. I built the environment with CloudFormation and was able to verify operation across all events: Viewer Request, Viewer Response, Origin Request, and Origin Response.
2025.11.28

This page has been translated by machine translation. View original

On November 25, 2025, AWS Lambda started supporting Node.js 24. While I've covered how it works with regular Lambda functions in a previous article,
this time I've verified its operation 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 has different constraints compared to regular Lambda.
Node.js 24 includes a breaking change with the deprecation of the callback format, but I've confirmed through hands-on testing that this change doesn't compromise the compatibility of Lambda@Edge's basic functionality (four event hooks and header manipulation).

Testing 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)

Lambda@Edge Function Implementation

I prepared Lambda@Edge functions for all four triggers.

Four triggers
Resizing Images with Amazon CloudFront & Lambda@Edge

1. Viewer Request Handler

Viewer Request executes immediately after CloudFront receives a request from a viewer. You can add request headers or rewrite URIs.

exports.handler = async (event) => {
  const request = event.Records[0].cf.request;

  // Add custom header
  request.headers['x-custom-header'] = [{ 
    key: 'X-Custom-Header', 
    value: 'Node24-Edge' 
  }];

  // URI rewrite example
  if (request.uri === '/old-path') {
    request.uri = '/new-path';
  }

  return request;
};

Constraints:

  • Timeout: 5 seconds
  • Memory: 128MB

2. Origin Request Handler

Origin Request executes just before CloudFront forwards the request to the origin. You can read headers added by CloudFront (like geolocation information).

exports.handler = async (event) => {
  const request = event.Records[0].cf.request;

  // Add header for 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 executes immediately after CloudFront receives a response from the origin. Used to add security headers, etc.

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 executes just before CloudFront returns the response to the viewer. Used to add cache control headers, etc.

exports.handler = async (event) => {
  const response = event.Records[0].cf.response;

  // Add cache control headers
  response.headers['cache-control'] = [{
    key: 'Cache-Control',
    value: 'public, max-age=3600'
  }];

  // Note: Status code changes are not allowed in Viewer Response

  return response;
};

Constraints:

  • Timeout: 5 seconds
  • Memory: 128MB
  • Status code changes not allowed

CloudFormation Template

I prepared a CloudFormation template to deploy all four Lambda@Edge functions and the CloudFront distribution 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:
      # Note: 123456789012 is a dummy AWS account ID. Please change it according to your environment.
      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

Reason for adopting Origin Shield:
Since Lambda@Edge runs at each edge location, logs are distributed across regions worldwide. By enabling Origin Shield, logs for Origin Request/Response events can be consolidated in us-east-1, making log verification and analysis easier.

Note when using CloudFormation ZipFile:
When using the ZipFile property in 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 that Lambda@Edge was associated in the CloudFront Behavior settings.

CloudFront_Behavior Settings

Deployment Procedure

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. Upload Test File

# Create test HTML file
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 environment
aws s3 cp test.html s3://node24-edge-origin-123456789012/test.html --region us-east-1

2. Access 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 confirmed the operation of Lambda@Edge functions as follows:

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 displayed in the response, so these were confirmed via CloudWatch Logs

Conclusion

Through this verification, I confirmed that the Node.js 24 runtime works properly in the Lambda@Edge environment and its basic functions are available.

Node.js 24 has long-term support (LTS) planned until April 2028 and offers improved execution performance at edge locations. I recommend it as the first choice for new development projects.

For existing Lambda@Edge functions that use the callback format, migration to async/await is mandatory. However, since Lambda@Edge functions tend to have less code, the cost of modification is likely to be relatively low.
That said, updating just before EoL carries unexpected risks. Please consider a Node.js 24 migration plan with plenty of lead time.

Share this article

FacebookHatena blogX

Related articles