Introduction Link to heading

I am of the strong opinion that infrastructure as code should actually, you know, manage your infrastructure. So you don’t have to.

That’s kind of the whole point of CDK.

Which is why it was so frustrating when our security team (reasonably!) demanded we update hundreds of Lambda functions to Node 22, and CDK’s NODEJS_LATEST was stuck on Node 18. And then Node 18 hit end-of-life in April 2025… and NODEJS_LATEST was still pointing at it.

But I have good news! This story has a happy ending. As of September 2025, NODEJS_LATEST Just Works. You can now enable one feature flag in your cdk.json, never think about Node runtime versions again. And yes, that means automatic upgrades to your runtime when you update CDK.

As a side note, I am appreciative and respectful of the CDK developers even though this was a rough ride on my side. Building and maintaining a framework as complex as CDK, addressing 36 regions multiplied by 8 partitions, each combination independently gaining features over time, while maintaining backward compatibility guarantees? That’s genuinely hard work.

The Problem Link to heading

The Scenario Link to heading

Picture this: You’ve got hundreds of CDK repos. Many featuring small Lambda functions - a couple hundred lines of code each. They’re not doing anything exotic. They just need to run on a supported Node version.

Your security team (doing their job) sends around the announcement: “Node 18 reaches end-of-life April 30, 2025. All functions must be migrated to Node 20 or 22.”

You groan. Because you know what this means: opening hundreds of pull requests, updating runtime: Runtime.NODEJS_18_X to runtime: Runtime.NODEJS_20_X in hundreds of Lambda function definitions across hundreds of repos.

This is exactly the kind of toil that infrastructure as code was supposed to eliminate.

The Broken Tool Link to heading

CDK has a constant called Runtime.NODEJS_LATEST. The implicit promise is pretty simple: just use the latest version. There’s also a feature flag called @aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion which is a little surprising. Why wouldn’t LATEST mean Latest? But ok.

The implied promise is simple: enable the flag, and your NodejsFunction constructs automatically use the latest available Node runtime. No manual version management required.

Back in Nov 2023, issue #28125 pointed out that NODEJS_LATEST was stuck on nodejs18.x even though Node 20 was already available in Lambda.

The situation got worse. Node 18 reached end-of-life on April 30, 2025. And NODEJS_LATEST was still pointing to it. For months, the “use latest runtime” feature flag was giving you an end-of-life, unsupported runtime. The security team was breathing down your neck, and you’re second-guessing your decision to use CDK.

Why It Was Stuck Link to heading

CDK has a constraint: NODEJS_LATEST must work in all AWS regions, including isolated partitions like China and GovCloud. They couldn’t update it until Node 22 was globally available everywhere.

Which is fair! But it meant months of lag between Lambda runtime availability in most regions and CDK catching up.

In May 2025, I opened PR #34117 purely out of frustration. The CDK team noted they were blocked on regional availability. The tool was broken, and there was no timeline for when it would be fixed.

The Workaround That Almost Worked Link to heading

There is a function in CDK called determineLatestNodeRuntime() that does the right thing - it looks up the latest Node runtime available in your specific AWS partition at synth time.

The problem? The API is slightly awkward:

import { determineLatestNodeRuntime } from 'aws-cdk-lib/aws-lambda-nodejs';

new NodejsFunction(this, 'MyFunction', {
  entry: 'lambda/handler.ts',
  runtime: determineLatestNodeRuntime(this),  // Needs the construct scope
});

You had to:

  1. Remember the function exists
  2. Import it separately
  3. Pass your stack as a parameter
  4. Do this for every. single. Lambda function.

Except… it’s not just you, it’s all the developers who are happily working away with CDK.

Not terrible, but also not “stop thinking about Node versions.” And not the kind of trivial change which can be easily rolled across 100’s of repos.

So we were stuck between:

  • Manual version management (hundreds of PRs, dozens of annoyed developers)
  • A broken NODEJS_LATEST (EOL runtimes)
  • An awkward workaround (better, but tedious)

The Happy Ending Link to heading

In September 2025, CDK shipped a fix. And it’s clever.

Runtime.NODEJS_LATEST now uses a sentinel value pattern. When you write:

new NodejsFunction(this, 'MyFunction', {
  entry: 'lambda/handler.ts',
  runtime: Runtime.NODEJS_LATEST,
});

Lambda Functions detect that you used Runtime.NODEJS_LATEST and automatically call determineLatestNodeRuntime() for you. You get partition-aware runtime selection without the awkward API.

Under the hood:

  • Runtime.NODEJS_LATEST is hardcoded to nodejs22.x (provides a sensible default)
  • But Lambda Functions intercept it: runtime === Runtime.NODEJS_LATEST?
  • And delegate to determineLatestNodeRuntime(this) which looks up the partition-specific latest

This means:

  • Commercial regions → nodejs22.x
  • Isolated partitions that don’t have Node 22 yet → nodejs18.x or nodejs20.x
  • Same code works everywhere

So Runtime.NODEJS_LATEST finally lives up to its name.

How to Actually Stop Thinking About Node Versions Link to heading

This is the whole point. Here’s what you do:

Step 1: Enable the Feature Flag Link to heading

Add this to your cdk.json:

{
  "context": {
    "@aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion": true
  }
}

Step 2: There Is No Step 2 Link to heading

That’s it. You’re done.

With the flag enabled, NodejsFunction automatically uses Runtime.NODEJS_LATEST as the default. You don’t need to specify runtime at all:

import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs';

new NodejsFunction(this, 'MyFunction', {
  entry: 'lambda/handler.ts',
  // No runtime specified = uses NODEJS_LATEST automatically
});

Well… the developer education side of it is mostly done. Cleanup is easy: search your code for runtime: Runtime.NODEJS.* and simply remove it.

When Node 24 comes out:

  1. AWS Lambda eventually deploys support, region by region (with considerations for partitions)
  2. CDK team updates the regional facts. Probably a few times as the regions land.
  3. You upgrade your CDK version (I mean… renovate updates it) Again, a few times over the duration of the rollout
  4. Redeploys trickle through your CD system
  5. Your Lambda functions are now on Node 24

No code manual changes. No manual version management. This is what infrastructure as code was supposed to do.

The Bundling Gotcha (Actually a Feature) Link to heading

When using NODEJS_LATEST, no modules are excluded from bundling by default.

Why? Because the runtime version can change between CDK releases. If you rely on packages pre-installed in the Lambda environment (like the old aws-sdk in Node 16/18), your function might break when NODEJS_LATEST updates.

But for simple Lambda functions - which is what most of us are writing - this is exactly what you want. Bundle everything, stop worrying about what’s pre-installed in the runtime environment.

If you’re in the rare situation where you need explicit runtime control (specific Node features, pre-installed packages, etc.), pin to a specific version:

runtime: Runtime.NODEJS_22_X,  // Won't change between CDK upgrades

Why This Matters Link to heading

Generally, I don’t really care what version of Node I’m running on. Because generally, I’m running very small Lambda functions - a couple hundred LoC or less. And, again, generally, we aren’t really doing anything that Node 8 wouldn’t have supported.

So… generally… what I really want is for Node runtime to Not Be A Problem.

And that’s what the current NODEJS_LATEST gives me.

Conclusion Link to heading

The NODEJS_LATEST saga has a happy ending. After over a year of frustration - from being stuck on Node 18 while Node 20 was available, through six months of pointing at an EOL runtime after April 2025 - it finally works properly as of September 2025.

The fix:

  1. Enable one feature flag in cdk.json

Boom. There it is.

References Link to heading