Introducing Node.js 24 Support
Ampt now supports Node.js 24 as the default runtime, bringing Web Streams, URLPattern, iterator helpers, and a pile of features that used to require an npm package.
Jeremy Daly

We've been testing Node.js 24 in preview for a while, and it's now the default runtime for all new Ampt apps. Existing apps can move over by setting "runtime": "nodejs24" in the ampt section of their package.json.
A lot of what's interesting in Node 24 is "previously needed an npm package, now it's built in." That kind of feature doesn't always get a lot of attention, but it makes your node_modules smaller and your code less dependent on third-party packages.
Web Streams that actually stream
ReadableStream, WritableStream, and TransformStream are globals now. Same APIs you'd use in the browser, no imports required.
javascriptimport { http } from '@ampt/sdk'; import express from 'express'; const app = express(); app.get('/ticks', async (_req, res) => { const stream = new ReadableStream({ async start(controller) { for (let i = 1; i <= 5; i++) { controller.enqueue(JSON.stringify({ tick: i, at: new Date() }) + '\n'); await new Promise((r) => setTimeout(r, 1000)); } controller.close(); }, }); res.set('Content-Type', 'text/plain'); res.set('Content-Encoding', 'identity'); const reader = stream.getReader(); for (;;) { const { value, done } = await reader.read(); if (done) break; res.write(value); } res.end(); }); http.node.use(app);
That actually streams end to end. From your function to the browser, one line at a time.
There's a small footnote. CloudFront sits in front of every Ampt app, and it buffers small responses while it decides whether to compress them. The Content-Encoding: identity header above tells it to skip the compress step. We're folding this into the runtime so future apps won't need to set the header by hand.
URL matching without a library
URLPattern is a global. You can pull route parameters out of any URL without path-to-regexp or whatever you've been using:
javascriptconst pattern = new URLPattern({ pathname: '/users/:id' }); const match = pattern.exec('https://example.com/users/42'); console.log(match.pathname.groups.id); // '42'
Useful for webhook routers, lightweight URL validation, and anywhere you need something more capable than String.includes but less heavyweight than a real router.
A safer way to build regex from user input
RegExp.escape() is now available. If you've ever written a search endpoint and had to copy-paste or write your own escapeRegExp helper, this should eliminate all the edge cases:
javascriptconst term = req.query.q ?? ''; const re = new RegExp(RegExp.escape(term), 'i'); const matches = corpus.filter((s) => re.test(s));
One less foot-gun in the search-endpoint pipeline.
Iterator helpers, finally lazy
You can chain .filter, .map, .take, and friends directly on any iterator now. They're lazy, which is the whole point:
javascriptfunction* naturals() { let i = 1; while (true) yield i++; } const firstFiveEvenSquares = naturals() .filter((n) => n % 2 === 0) .map((n) => n * n) .take(5) .toArray(); // [4, 16, 36, 64, 100]
No intermediate arrays. The chain pulls one value at a time, applies the transformations, stops when take(5) is satisfied. If you've been reaching for libraries like iter-tools to get this, you can drop the dependency.
Outbound WebSocket without the ws package
new WebSocket(url) works in any Ampt function. No imports.
javascriptconst ws = new WebSocket('wss://api.example.com/stream'); ws.addEventListener('open', () => ws.send('hello')); ws.addEventListener('message', (e) => console.log(e.data));
Useful if you're talking to realtime APIs from a serverless function. LLM providers with WebSocket transports, market data feeds, anything that pushes data at you. The API is browser-compatible too, so the same code runs in the browser unchanged.
AsyncLocalStorage stops being a foot-gun
This one needs context. AsyncLocalStorage is what most frameworks use for request-scoped state. Trace IDs, tenant context, per-request loggers. The well-known warning has always been: don't use enterWith() on Lambda, because the warm-start invocation model can leak state from one request into the next.
We pressure tested this. A Node 24 Ampt deployment took alternating requests on the same warm container, half deliberately calling enterWith() to try to leak a value and half checking whether anything got through. Zero leaks. Then we forced Node back onto the legacy AsyncLocalStorage implementation (the one the warning was originally written about) and ran the same test. Still zero leaks.
The reason is the runtime architecture. Every Ampt invocation runs inside its own AsyncLocalStorage.run() bubble at the platform level. When the bubble closes, anything you attached to the async chain inside it goes with it. Combined with Node 24's improved context propagation and AWS Lambda's own invocation isolation, the classic foot-gun isn't really a foot-gun on this stack anymore.
Use enterWith() if it makes your code cleaner.
Smaller embeddings with Float16Array
If your app does anything with vector embeddings (RAG, semantic search, classification), you've probably been wondering whether you really need 32 bits per dimension. Float16Array is half the memory at 16 bits, which adds up fast when you're holding thousands of vectors:
javascriptconst embedding = new Float16Array(1536); // OpenAI ada-002 dimension console.log(embedding.byteLength); // 3072 bytes (vs 6144 for Float32Array)
Most embedding workloads don't need the extra precision. Cuts your memory roughly in half.
ES modules from CommonJS code
Node 24 made require() work for ESM modules. Mixed dependency trees stop being a battle.
If you've ever installed a fresh package and gotten Error: require() of ES Module in your CommonJS code, that's gone. The library is ESM-only? Fine. require() it.
A few quality-of-life things
fs.globSync('*.{ts,json}')gives you built-in glob matching, drop yourglobdependency for simple casesArray.fromAsync(asyncIterable)drains a paginated async generator into an array in one call- The regex
/vflag adds set notation,\p{RGI_Emoji}, and intersection and subtraction in character classes dirent.parentPathis the new name for the deprecateddirent.pathonreaddir({ withFileTypes: true })- OpenSSL 3.x and Undici 7 are the floor for crypto and outbound HTTP
Heads-up: a few APIs are gone
Node 24 finally retired some long-deprecated helpers. Most importantly: util.isBoolean, util.isString, util.isNumber, and util.isObject. If your code (or a dependency) still calls them, you'll see a runtime error.
The fix is almost always one line. typeof x === 'boolean' or Array.isArray(x). Worth a quick grep before you flip the runtime.
tls.createSecurePair is also gone. That one's been deprecated since Node 6 and almost nobody uses it directly, but if you do, you'll need tls.TLSSocket.
Migrating your Ampt apps to Node.js 24
To migrate existing Ampt apps to Node.js 24, update the runtime in the ampt section of your package.json to nodejs24:
json{ "ampt": { "runtime": "nodejs24" } }
Redeploy and you're on it.
note
Node.js 24 is now the default runtime for all new Ampt apps.
For more information about Node.js 24, see the official release announcement and documentation.