DEV Community

Cover image for Node.js 26 LTS: planning the Lambda upgrade
Sascha Mirow-Stanitzok
Sascha Mirow-Stanitzok

Posted on Originally published at sascha-ms.de

Node.js 26 LTS: planning the Lambda upgrade

Node.js 26 LTS has been official since October 28, 2026, under the codename “Lithium”. For me, an LTS switch like this is always the moment to stop eyeing the next release and write an upgrade plan instead. If you only touch your Lambda functions once the deprecation email from AWS lands in your inbox, you work under time pressure. And time pressure is a pretty bad advisor for runtime upgrades.

On AWS Lambda, the nodejs26.x runtime is already available as a public preview, and according to the documentation AWS is targeting general availability for November 2026. That window is valuable: you can test today without having to switch production today. I will show you what Node.js 26 brings, what breaks when you move from Node.js 22 or 24, and how a small probe function for a fictional bike rental tells you what the new Lambda runtime can really do.

In short

Node.js 26 became LTS on October 28, 2026 and receives updates until the end of April 2029, while nodejs26.x on Lambda is still a preview with a planned launch in November 2026. The most useful addition for everyday work is the Temporal API for dates and times. The biggest pitfall: what works under Node 26 on your laptop is not necessarily enabled the same way in the Lambda runtime, so check features there directly instead of assuming them.

What you will learn in this article

  • What Node.js 26 LTS means for your support timeline on Lambda
  • Which additions such as Temporal and Map.getOrInsert you can use right away
  • Which changes can break when you upgrade from Node.js 22 or 24
  • How to test nodejs26.x next to nodejs24.x with AWS CDK
  • How to find every Node runtime in your AWS account

What Node.js 26 LTS means for your timeline

LTS stands for Long Term Support: a version receives security and bug fixes over a long period, and new features only arrive cautiously. Release 26.13.0, which officially starts Node.js 26 LTS, is purely formal. According to the release notes, it only switches metadata such as process.release to LTS and contains nothing else beyond 26.12.1. Updates for Node.js 26 LTS continue until the end of April 2029.

The Node.js project is also changing its rhythm. Until now, only even-numbered versions became LTS, while odd ones such as Node.js 25 were done after six months (25 reached EOL, or “end of life”, at the end of March 2026). Starting with Node.js 27, there will be one major version per year, and each of them becomes LTS after its six-month Current phase. For teams, that means fewer versions and a predictable cadence.

Here is where Lambda stands at the end of October 2026:

Runtime Node.js line Status on Lambda
nodejs22.x LTS “Jod” Deprecated on Apr 30, 2027, updates blocked from Aug 31, 2027
nodejs24.x LTS “Krypton” Deprecated on Apr 30, 2028
nodejs26.x LTS “Lithium” Public preview, general availability planned for November 2026

Timeline of Node.js runtimes on AWS Lambda: nodejs22.x until April 2027, nodejs24.x until April 2028, nodejs26.x from November 2026

For many teams, the date that matters most is the end of nodejs22.x in April 2027.

My take: for many teams, the real deadline is not the launch of Node.js 26 but the deprecation of nodejs22.x. That leaves roughly six months. If you plan now, you can fit the upgrade into your normal sprint cadence.

Node.js 26 LTS features you will notice day to day

Coming from Node.js 24, you get not only the changes in Node.js 26 LTS but also everything from Node.js 25. That intermediate version was never LTS, so many teams skipped it. A few points stand out for daily work.

Temporal: dates and times without the headache

Node.js 26 LTS ships the Temporal API enabled by default, no flag required. Temporal is the new standard for dates and times in JavaScript and fixes the weaknesses of Date: months that start at 0, no real time zone arithmetic, and objects you can mutate by accident. Instead, you get clear types. Temporal.Instant is an exact point in time, Temporal.ZonedDateTime is a point in time in a specific time zone, and Temporal.PlainDate is a plain calendar date.

const pickup = Temporal.ZonedDateTime.from("2026-10-24T10:00[Europe/Berlin]");
const due = pickup.add({ days: 2 });

console.log(due.toString());
// 2026-10-26T10:00:00+01:00[Europe/Berlin]  (same wall-clock time, even though the DST change happens in between)
Enter fullscreen mode Exit fullscreen mode

We will use exactly this example in the probe function later. With Date and “plus 48 hours”, you end up at 9 a.m. instead of 10 a.m. on October 26, because the clocks go back during the night before October 25. I cover how to use Temporal day to day for deadlines, time zones and DynamoDB in detail in Temporal API in TypeScript: deadlines and time zones.

Small helpers: getOrInsert, Iterator.concat and Base64

V8 14.6 adds two small but pleasant functions. Map.prototype.getOrInsert() and getOrInsertComputed() read a value or create it if it is missing. The classic “does this key exist yet?” pattern for grouping shrinks to a single line. Iterator.concat() chains several iterators without building intermediate arrays.

const rentals = [
  { station: "Central Station", bikeId: "city-bike-17" },
  { station: "Old Town", bikeId: "e-bike-03" },
  { station: "Central Station", bikeId: "cargo-bike-02" },
];

const byStation = new Map<string, string[]>();
for (const rental of rentals) {
  byStation.getOrInsert(rental.station, []).push(rental.bikeId);
}
// Map { "Central Station" => ["city-bike-17", "cargo-bike-02"], "Old Town" => ["e-bike-03"] }
Enter fullscreen mode Exit fullscreen mode

Node.js 25 contributed, for example, Uint8Array.fromBase64() and toBase64() (plus hex variants) and a noticeably faster JSON.stringify. In Lambda functions that build lots of JSON responses or encode binary data for API Gateway, that is a nice bonus for free. One observation from my test: with "lib": ["esnext"], TypeScript 7.0.2 already knows the types for Temporal, getOrInsert and fromBase64, but still reports an error for Iterator.concat.

Platform: Undici 8, Web Storage and permissions

Undici, the built-in HTTP client behind fetch, moves to version 8. Since Node.js 25, the Web Storage API is enabled by default, but in Node.js 26 localStorage returns undefined as long as no storage file is configured. Since Node.js 25, the permission model also knows --allow-net, which lets you restrict a process’s network access. On Lambda, that matters more for local tools and CI scripts, because isolation there is handled by IAM and VPC anyway.

What breaks when you move to Node.js 26 LTS

Most projects keep running after the upgrade to Node.js 26 LTS. Still, there are a handful of changes worth checking deliberately before you switch the runtime:

  • The old internal modules _stream_readable, _stream_writable and their siblings are removed in Node.js 26. Older libraries sometimes still import them directly.
  • http.Server.prototype.writeHeader() is gone, use writeHead().
  • Since Node.js 25, fs.F_OK, fs.R_OK, fs.W_OK and fs.X_OK are no longer exported directly from the module. The constants live under fs.constants.
  • SlowBuffer and assert.CallTracker have reached end of life since Node.js 25.
  • Corepack is no longer bundled since Node.js 25. If you enable pnpm or Yarn via Corepack, install it separately in CI.
  • module.register() emits a runtime deprecation warning in Node.js 26, and --experimental-transform-types was removed.
  • Native addons need a rebuild, because NODE_MODULE_VERSION changes to 147.
  • Since Node.js 25, the official Linux binaries are linked against libatomic. Slim container images for CI may be missing that package.

On top of that on Lambda: what has applied since nodejs24.x

If you jump from nodejs22.x straight to 26, you also pick up the Lambda-specific changes AWS introduced with nodejs24.x. Callback handlers are no longer supported, context.callbackWaitsForEmptyEventLoop is removed, and so are context.succeed, fail and done. In addition, Lambda no longer waits for pending promises once your handler returns. Anything you still want to finish has to be completed with await first.

// Before: callback handler (no longer works from nodejs24.x)
export const handler = (event, context, callback) => {
  loadBike(event.bikeId)
    .then((bike) => callback(null, bike))
    .catch((err) => callback(err));
};

// After: async handler, every piece of work is awaited
export const handler = async (event: { bikeId: string }) => {
  const bike = await loadBike(event.bikeId);
  await auditLog.write({ action: "lookup", bikeId: bike.id }); // without await this would get lost
  return bike;
};
Enter fullscreen mode Exit fullscreen mode

One more point is easy to miss: Lambda disables experimental Node.js features by default, such as require() of ES modules and automatic module detection. You can turn them back on via NODE_OPTIONS, but AWS covers such cases with neither support nor the SLA.

nodejs26.x on Lambda: the state at the end of October 2026

The nodejs26.x runtime is available as a public preview, both as a ZIP runtime and as the container base image public.ecr.aws/lambda/nodejs:26-preview. Preview means no SLA, no technical support, and breaking changes are possible. It is explicitly not meant for production workloads. AWS names November 2026 as the launch of the stable version, but only as a non-binding estimate.

The feedback issue for the preview on GitHub is revealing. Users report, among other things, that Temporal is missing in the preview and that process.features.require_module is false. These are community observations, not an official AWS statement, and a lot can change before the stable release. I still think the lesson matters: a Lambda runtime is not automatically identical to the Node.js on your machine. So let us check directly in Lambda.

🎬 Probe function on two Lambda runtimes – this animated diagram only runs on my site: watch it on sascha-ms.de

Step by step: testing nodejs26.x next to nodejs24.x

Our example is a fictional bike rental. A function calculates when a rented bike has to be back: same time of day, a given number of days later, in the time zone of the rental station. It also returns a small report on which features the runtime really provides. If Temporal is built in, the function uses it, otherwise a polyfill steps in. A polyfill is a retrofitted implementation of a standard API for environments that do not support it yet.

Prerequisites

  • Node.js 22 or newer locally (thanks to the polyfill, the tests run without Node.js 26 too)
  • AWS CLI v2 with credentials for a test or sandbox account
  • An account bootstrapped with npx cdk bootstrap in your region
  • A region where nodejs26.x is already selectable. You can see that in the Lambda console when creating a function.

Step 1: create the project

mkdir bike-rental-runtime-check && cd bike-rental-runtime-check
npm init -y
npm install @js-temporal/polyfill@0.5.1
npm install -D typescript@7.0.2 @types/node@26 @types/aws-lambda tsx esbuild aws-cdk-lib@2.275.0 constructs aws-cdk@2.1146.1
mkdir -p bin lib src test
Enter fullscreen mode Exit fullscreen mode

Switch package.json to ES modules and add a few scripts:

{
  "name": "node26-lambda-upgrade",
  "version": "1.0.0",
  "scripts": {
    "typecheck": "tsc --noEmit -p .",
    "test": "node --import tsx --test test/*.test.ts",
    "synth": "cdk synth",
    "deploy": "cdk deploy"
  },
  "license": "MIT",
  "devDependencies": {
    "@types/aws-lambda": "^8.10.164",
    "@types/node": "^26.6.6",
    "aws-cdk": "^2.1146.1",
    "aws-cdk-lib": "^2.275.0",
    "constructs": "^10.8.1",
    "esbuild": "^0.28.2",
    "tsx": "^4.23.15",
    "typescript": "^7.0.2"
  },
  "dependencies": {
    "@js-temporal/polyfill": "^0.5.1"
  },
  "private": true,
  "type": "module"
}
Enter fullscreen mode Exit fullscreen mode

Step 2: configure TypeScript

With "lib": ["esnext"], TypeScript knows the new APIs such as Temporal. noEmit is enough, because esbuild bundles during deployment and tsx runs the tests directly.

// tsconfig.json
{
  "compilerOptions": {
    "target": "es2024",
    "lib": ["esnext"],
    "module": "nodenext",
    "moduleResolution": "nodenext",
    "strict": true,
    "types": ["node"],
    "noEmit": true,
    "skipLibCheck": true
  },
  "include": ["bin", "lib", "src", "test"]
}
Enter fullscreen mode Exit fullscreen mode

Step 3: write the runtime report

The function checks every feature directly instead of inferring it from the version number. That is more reliable, because a runtime can have a feature disabled despite a matching version.

// src/runtime-report.ts
// Reports which runtime features are actually available in the current Node.js environment.

export interface RuntimeReport {
  nodeVersion: string;
  ltsName: string | null;
  temporal: boolean;
  mapGetOrInsert: boolean;
  iteratorConcat: boolean;
  uint8ArrayBase64: boolean;
  requireModule: boolean;
  lambdaRuntime: string | null;
}

export function collectRuntimeReport(env: NodeJS.ProcessEnv = process.env): RuntimeReport {
  const g = globalThis as Record<string, unknown>;
  const iteratorCtor = g.Iterator as { concat?: unknown } | undefined;

  return {
    nodeVersion: process.version,
    ltsName: process.release.lts ?? null,
    temporal: typeof g.Temporal === "object" && g.Temporal !== null,
    mapGetOrInsert: typeof (Map.prototype as { getOrInsert?: unknown }).getOrInsert === "function",
    iteratorConcat: typeof iteratorCtor?.concat === "function",
    uint8ArrayBase64: typeof (Uint8Array as { fromBase64?: unknown }).fromBase64 === "function",
    requireModule: process.features.require_module === true,
    // In Lambda, AWS_EXECUTION_ENV looks like this, for example: AWS_Lambda_nodejs24.x
    lambdaRuntime: env.AWS_EXECUTION_ENV?.replace("AWS_Lambda_", "") ?? null,
  };
}
Enter fullscreen mode Exit fullscreen mode

Step 4: load Temporal with a fallback

// src/temporal.ts
// Uses the built-in Temporal if the runtime provides it, otherwise the polyfill.

export type TemporalApi = typeof Temporal;

export async function loadTemporal(): Promise<{ api: TemporalApi; source: "runtime" | "polyfill" }> {
  const builtIn = (globalThis as { Temporal?: TemporalApi }).Temporal;
  if (builtIn) {
    return { api: builtIn, source: "runtime" };
  }
  const polyfill = await import("@js-temporal/polyfill");
  return { api: polyfill.Temporal as unknown as TemporalApi, source: "polyfill" };
}
Enter fullscreen mode Exit fullscreen mode

The dynamic import ensures that the polyfill is only loaded when it is needed. esbuild still bundles it, which makes the package about 150 KB larger. Once Temporal is reliably available in the stable runtime, you can remove the fallback.

Step 5: the rental logic

// src/rental.ts
// Fictional bike rental: when does a bike have to be back?

import type { TemporalApi } from "./temporal.js";

export interface RentalRequest {
  bikeId: string;
  pickupAt: string; // pickup time as an instant, e.g. "2026-10-24T08:00:00Z"
  days: number; // number of rental days
  timeZone: string; // time zone of the rental station, e.g. "Europe/Berlin"
}

export function validateRental(input: Partial<RentalRequest>): RentalRequest {
  const { bikeId, pickupAt, days, timeZone } = input;
  if (!bikeId || !pickupAt || !timeZone) {
    throw new Error("bikeId, pickupAt and timeZone are required");
  }
  if (typeof days !== "number" || !Number.isInteger(days) || days < 1 || days > 30) {
    throw new Error("days must be an integer between 1 and 30");
  }
  return { bikeId, pickupAt, days, timeZone };
}

// With Temporal, "2 days" means the same wall-clock time two calendar days later, even across a DST change.
export function returnDeadline(T: TemporalApi, req: RentalRequest): string {
  return T.Instant.from(req.pickupAt)
    .toZonedDateTimeISO(req.timeZone)
    .add({ days: req.days })
    .toString({ smallestUnit: "minute" });
}

// The classic Date approach just adds milliseconds. Across a DST change it is off by one hour.
export function naiveReturnDeadline(req: RentalRequest): string {
  const start = new Date(req.pickupAt).getTime();
  return new Date(start + req.days * 24 * 60 * 60 * 1000).toISOString();
}
Enter fullscreen mode Exit fullscreen mode

Step 6: the Lambda handler

// src/handler.ts
// Lambda handler: returns the runtime report and the return deadline for a rental.

import { collectRuntimeReport } from "./runtime-report.js";
import { loadTemporal } from "./temporal.js";
import { naiveReturnDeadline, returnDeadline, validateRental, type RentalRequest } from "./rental.js";

const DEMO_RENTAL: RentalRequest = {
  bikeId: "city-bike-17",
  pickupAt: "2026-10-24T08:00:00Z",
  days: 2,
  timeZone: "Europe/Berlin",
};

// Deliberately async without a callback: since nodejs24.x, Lambda no longer supports callback handlers.
export const handler = async (event: Partial<RentalRequest> = {}) => {
  const rental = validateRental({ ...DEMO_RENTAL, ...event });
  const { api, source } = await loadTemporal();

  return {
    runtime: collectRuntimeReport(),
    temporalSource: source,
    rental: {
      bikeId: rental.bikeId,
      returnAt: returnDeadline(api, rental),
      naiveReturnAtUtc: naiveReturnDeadline(rental),
    },
  };
};
Enter fullscreen mode Exit fullscreen mode

Step 7: tests with the Node test runner

The tests cover the interesting case: a rental across the DST change on October 25, 2026. This shows in black and white why Temporal is worth the switch.

// test/rental.test.ts
import { test } from "node:test";
import assert from "node:assert/strict";
import { loadTemporal } from "../src/temporal.js";
import { naiveReturnDeadline, returnDeadline, validateRental } from "../src/rental.js";

const overDstChange = validateRental({
  bikeId: "city-bike-17",
  pickupAt: "2026-10-24T08:00:00Z", // 10:00 in Berlin (summer time)
  days: 2,
  timeZone: "Europe/Berlin",
});

test("Temporal keeps the wall-clock time across the DST change", async () => {
  const { api } = await loadTemporal();
  assert.equal(returnDeadline(api, overDstChange), "2026-10-26T10:00+01:00[Europe/Berlin]");
});

test("Date arithmetic ends up one hour early after the DST change", () => {
  // 08:00 UTC on Oct 26 is 09:00 winter time in Berlin, not 10:00
  assert.equal(naiveReturnDeadline(overDstChange), "2026-10-26T08:00:00.000Z");
});

test("without a DST change both approaches agree", async () => {
  const { api } = await loadTemporal();
  const normalWeek = { ...overDstChange, pickupAt: "2026-11-02T09:00:00Z" };
  assert.equal(returnDeadline(api, normalWeek), "2026-11-04T10:00+01:00[Europe/Berlin]");
  assert.equal(naiveReturnDeadline(normalWeek), "2026-11-04T09:00:00.000Z");
});

test("invalid rental duration is rejected", () => {
  assert.throws(() => validateRental({ ...overDstChange, days: 0 }), /days must/);
  assert.throws(() => validateRental({ ...overDstChange, days: 1.5 }), /days must/);
});
Enter fullscreen mode Exit fullscreen mode
// test/runtime-report.test.ts
import { test } from "node:test";
import assert from "node:assert/strict";
import { collectRuntimeReport } from "../src/runtime-report.js";
import { handler } from "../src/handler.js";

test("report contains the Node version and detects the Lambda runtime", () => {
  const report = collectRuntimeReport({ AWS_EXECUTION_ENV: "AWS_Lambda_nodejs26.x" });
  assert.match(report.nodeVersion, /^v\d+\./);
  assert.equal(report.lambdaRuntime, "nodejs26.x");
});

test("outside of Lambda, lambdaRuntime is null", () => {
  assert.equal(collectRuntimeReport({}).lambdaRuntime, null);
});

test("handler returns report and return deadline", async () => {
  const result = await handler({ days: 3 });
  assert.equal(result.rental.bikeId, "city-bike-17");
  assert.equal(result.rental.returnAt, "2026-10-27T10:00+01:00[Europe/Berlin]");
  assert.ok(["runtime", "polyfill"].includes(result.temporalSource));
});
Enter fullscreen mode Exit fullscreen mode

Step 8: the CDK stack with two runtimes

The installed version of aws-cdk-lib (2.275.0) does not ship nodejs26.x as a ready-made constant yet. That is not a problem, because new Runtime(...) lets you define any runtime yourself. The stack creates the same function twice. If you have not used AWS CDK with TypeScript before, my introduction Lambda function with CDK and TypeScript will help.

// lib/runtime-check-stack.ts
import * as path from "node:path";
import { CfnOutput, Duration, RemovalPolicy, Stack, type StackProps } from "aws-cdk-lib";
import { Architecture, Runtime, RuntimeFamily } from "aws-cdk-lib/aws-lambda";
import { NodejsFunction, OutputFormat } from "aws-cdk-lib/aws-lambda-nodejs";
import { LogGroup, RetentionDays } from "aws-cdk-lib/aws-logs";
import type { Construct } from "constructs";

// aws-cdk-lib does not ship a constant for nodejs26.x yet, so we define it ourselves.
const NODEJS_26_PREVIEW = new Runtime("nodejs26.x", RuntimeFamily.NODEJS, { supportsInlineCode: true });

export class RuntimeCheckStack extends Stack {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);

    const variants = [
      { name: "Node24", runtime: Runtime.NODEJS_24_X, target: "node24" },
      { name: "Node26", runtime: NODEJS_26_PREVIEW, target: "node26" },
    ];

    for (const variant of variants) {
      const logGroup = new LogGroup(this, `${variant.name}Logs`, {
        retention: RetentionDays.ONE_WEEK,
        removalPolicy: RemovalPolicy.DESTROY,
      });

      const fn = new NodejsFunction(this, `RentalCheck${variant.name}`, {
        entry: path.join(import.meta.dirname, "../src/handler.ts"),
        handler: "handler",
        runtime: variant.runtime,
        architecture: Architecture.ARM_64,
        memorySize: 256,
        timeout: Duration.seconds(10),
        logGroup,
        bundling: {
          format: OutputFormat.ESM,
          target: variant.target,
          minify: true,
          sourceMap: true,
          // esbuild needs a require function in the ESM bundle for CommonJS dependencies
          banner: "import { createRequire } from 'module'; const require = createRequire(import.meta.url);",
        },
        environment: {
          NODE_OPTIONS: "--enable-source-maps",
        },
      });

      new CfnOutput(this, `${variant.name}FunctionName`, { value: fn.functionName });
    }
  }
}
Enter fullscreen mode Exit fullscreen mode
// bin/app.ts
import { App } from "aws-cdk-lib";
import { RuntimeCheckStack } from "../lib/runtime-check-stack.js";

const app = new App();
new RuntimeCheckStack(app, "BikeRentalRuntimeCheck", {
  env: { region: process.env.CDK_DEFAULT_REGION ?? "eu-central-1" },
});
Enter fullscreen mode Exit fullscreen mode
// cdk.json
{
  "app": "npx tsx bin/app.ts"
}
Enter fullscreen mode Exit fullscreen mode

Step 9: check, deploy and compare

npm run typecheck
npm test
npx cdk synth
npx cdk deploy
Enter fullscreen mode Exit fullscreen mode

After the deployment, CDK prints both function names. Call both with the same request and compare the responses:

aws lambda invoke --function-name <Node24FunctionName> \
  --cli-binary-format raw-in-base64-out --payload '{"days":2}' node24.json
aws lambda invoke --function-name <Node26FunctionName> \
  --cli-binary-format raw-in-base64-out --payload '{"days":2}' node26.json
diff <(jq .runtime node24.json) <(jq .runtime node26.json)
Enter fullscreen mode Exit fullscreen mode

This is the response when I call the bundled package locally under Node.js 22. Under nodejs26.x you expect a 26.x version, and exactly this report tells you which features are true there:

{
  "runtime": {
    "nodeVersion": "v22.23.4",
    "ltsName": "Jod",
    "temporal": false,
    "mapGetOrInsert": false,
    "iteratorConcat": false,
    "uint8ArrayBase64": false,
    "requireModule": true,
    "lambdaRuntime": "nodejs24.x"
  },
  "temporalSource": "polyfill",
  "rental": {
    "bikeId": "city-bike-17",
    "returnAt": "2026-10-26T10:00+01:00[Europe/Berlin]",
    "naiveReturnAtUtc": "2026-10-26T08:00:00.000Z"
  }
}
Enter fullscreen mode Exit fullscreen mode

The rental field is what matters: the Temporal calculation returns 10 a.m., while the naive Date calculation lands at 9 a.m. local time. On both runtimes, returnAt has to be identical, no matter whether Temporal is built in or the polyfill does the math.

Common mistakes

  • Runtime rejected: if your region does not offer the preview yet, the deployment fails. For the test, switch to a region where nodejs26.x is selectable in the console.
  • “Dynamic require … is not supported”: this message appears when an ESM bundle contains a CommonJS dependency. The banner with createRequire in the stack prevents it.
  • Callback handlers: old code with (event, context, callback) no longer works from nodejs24.x. Switch to async first.
  • Green locally, different in Lambda: your tests may run with the polyfill locally and with built-in Temporal in Lambda, or the other way round. That is exactly why the report checks every feature in the real runtime.

Cost and security

Two small 256 MB functions that you call a few times cost practically nothing and usually stay within the free tier. With the Lambda cost calculator you can do the math for your real load profiles. Logs are deleted after one week, and npx cdk destroy removes everything again. Do not process real customer data in the preview runtime, since there is neither an SLA nor support for it.

Upgrade checklist for your team

Find the Node runtimes in your account

Before you plan, you need a list. This command shows every function with a Node runtime per region:

for region in eu-central-1 eu-west-1; do
  aws lambda list-functions --region "$region" \
    --query "Functions[?starts_with(Runtime, 'nodejs')].[FunctionName, Runtime]" \
    --output text | sed "s/^/$region\t/"
done
Enter fullscreen mode Exit fullscreen mode

Update TypeScript, bundler and CI

Bump @types/node to version 26, set the matching target in esbuild or on NodejsFunction, and add a Node.js 26 entry to your CI test matrix. Rebuild native addons. And if you use pnpm or Yarn via Corepack, install Corepack explicitly in the pipeline, since it is no longer bundled.

Roll out gradually

Once nodejs26.x is generally available, do not switch all functions at once. I would start with non-critical functions and use an alias with weighted routing to send only part of the traffic to the new version. New runtimes often have somewhat longer cold starts at launch, so keep an eye on error rate and init duration. How to build alarms and dashboards for that is covered in Monitoring Lambda with CDK.

My take

Testing: yes, now. Production on the preview: no. That is my position in a nutshell. The preview is a gift, because you find the surprises while nobody is rushing you yet. If you still run on nodejs22.x today, though, I would not wait for November. The move to 24 brings the biggest Lambda-specific breaks, namely callback handlers and promises that are not awaited. Those affect 26 just the same. Once that is done, the step from 24 to Node.js 26 LTS is small in most projects.

Temporal is what I am looking forward to most. I would rather not count how many time zone and daylight saving bugs I have seen in my career. At the same time, the preview shows that Lambda runtimes are deliberately more conservative than upstream Node.js. For stability, I think that is right, but it can catch you off guard. So my advice: check features in the runtime where your code will actually run. And while you are at it, take a look at your patch routine, because security updates like the one for the Node.js vulnerability CVE-2026-111497 arrive regularly for LTS versions too.

By the way, I like the new annual cadence starting with Node.js 27. A team can schedule one LTS upgrade per year instead of debating every time which version to skip.

FAQ

When will Node.js 26 be generally available on AWS Lambda?

AWS names November 2026 as the planned launch for nodejs26.x, explicitly as a non-binding estimate. Until then, the runtime is a public preview without SLA or support.

How long is Node.js 26 LTS supported?

According to the Node.js project, the 26.x line receives updates until the end of April 2029. When AWS will deprecate the Lambda runtime has not been announced yet.

Can I already use Temporal in Lambda?

Yes, via a polyfill such as @js-temporal/polyfill on any Node runtime. Whether Temporal is built in and enabled in nodejs26.x is best checked with feature detection as in the example.

Do I have to upgrade from Node.js 22 to 24 first?

No, you can go straight to 26. But then you have to handle all changes from 24 and 26 at once. Since nodejs22.x is deprecated in April 2027, going through 24 now is often the more relaxed path.

Your next step

Run the command from the checklist today and count how many functions still run on nodejs22.x. Then deploy the probe function to a sandbox account and check in the report what Node.js 26 LTS really brings in nodejs26.x. I would like to know: which Node runtime are your Lambdas on right now, and what slows you down most when upgrading? Let me know in the comments.

Sources

This post was created with AI assistance.


👉 Next step: Curious what your own Lambda functions cost? Try my Lambda cost calculator – it runs right in your browser.

Originally published on sascha-ms.de. More articles on serverless, AI in everyday development and leading tech teams: sascha-ms.de/en

Top comments (0)