From 0df6cc38e4249e57bf72eebedd226bad32f35d3b Mon Sep 17 00:00:00 2001 From: Yagiz Nizipli Date: Tue, 7 Oct 2025 19:15:27 -0400 Subject: [PATCH 1/3] improve performance of responses --- .changeset/twelve-nails-agree.md | 5 +++++ .github/actions/pnpm-setup/action.yml | 4 ++-- .github/workflows/e2e.yml | 6 +++--- .github/workflows/pre-release.yml | 2 +- .github/workflows/v2-release.yml | 2 +- packages/open-next/src/core/routing/util.ts | 13 +++++++++---- packages/open-next/src/http/openNextResponse.ts | 16 +++++++++++++--- packages/tests-unit/tests/adapters/cache.test.ts | 6 +++--- .../tests-unit/tests/core/routing/util.test.ts | 1 + 9 files changed, 38 insertions(+), 17 deletions(-) create mode 100644 .changeset/twelve-nails-agree.md diff --git a/.changeset/twelve-nails-agree.md b/.changeset/twelve-nails-agree.md new file mode 100644 index 000000000..193a30535 --- /dev/null +++ b/.changeset/twelve-nails-agree.md @@ -0,0 +1,5 @@ +--- +"@opennextjs/aws": patch +--- + +Improves the performance of generating responses diff --git a/.github/actions/pnpm-setup/action.yml b/.github/actions/pnpm-setup/action.yml index 2355da8ef..13357de77 100644 --- a/.github/actions/pnpm-setup/action.yml +++ b/.github/actions/pnpm-setup/action.yml @@ -6,9 +6,9 @@ runs: steps: # Install nodejs. https://github.com/actions/setup-node - name: Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v5 with: - node-version: 18.x + node-version: 20.x # Install pnpm. https://github.com/pnpm/action-setup - uses: pnpm/action-setup@v4 diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index d903a6025..4ac03e7a1 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -90,11 +90,11 @@ jobs: with: version: 9 - - name: Set up NodeJS v18 - uses: actions/setup-node@v4 + - name: Set up NodeJS v20 + uses: actions/setup-node@v5 with: cache: pnpm # cache pnpm store - node-version: 18.18.2 + node-version: 20.19.5 - name: Install packages run: pnpm install diff --git a/.github/workflows/pre-release.yml b/.github/workflows/pre-release.yml index 96a56d054..8d250ade4 100644 --- a/.github/workflows/pre-release.yml +++ b/.github/workflows/pre-release.yml @@ -20,7 +20,7 @@ jobs: uses: actions/checkout@v4 # Setup .npmrc file to publish to npm - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v5 with: registry-url: "https://registry.npmjs.org" diff --git a/.github/workflows/v2-release.yml b/.github/workflows/v2-release.yml index a26e52811..41550c0a5 100644 --- a/.github/workflows/v2-release.yml +++ b/.github/workflows/v2-release.yml @@ -12,7 +12,7 @@ jobs: uses: actions/checkout@v4 # Setup .npmrc file to publish to npm - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v5 with: registry-url: "https://registry.npmjs.org" diff --git a/packages/open-next/src/core/routing/util.ts b/packages/open-next/src/core/routing/util.ts index 99aa45850..e0d7c9fc8 100644 --- a/packages/open-next/src/core/routing/util.ts +++ b/packages/open-next/src/core/routing/util.ts @@ -1,7 +1,6 @@ import crypto from "node:crypto"; import type { OutgoingHttpHeaders } from "node:http"; import { parse as parseQs, stringify as stringifyQs } from "node:querystring"; -import { Readable } from "node:stream"; import { BuildId, HtmlPages, NextConfig } from "config/index.js"; import type { IncomingMessage } from "http/index.js"; @@ -18,6 +17,7 @@ import type { StreamCreator, } from "types/open-next.js"; +import { ReadableStream } from "node:stream/web"; import { debug, error } from "../../adapters/logger.js"; import { isBinaryContentType } from "../../utils/binary.js"; import { localizePath } from "./i18n/index.js"; @@ -105,9 +105,14 @@ export function convertRes(res: OpenNextNodeResponse): InternalResult { const isBase64Encoded = isBinaryContentType(headers["content-type"]) || !!headers["content-encoding"]; - // We cannot convert the OpenNextNodeResponse to a ReadableStream directly - // You can look in the `aws-lambda.ts` file for some context - const body = Readable.toWeb(Readable.from(res.getBody())); + const body = + res._chunks == null + ? new ReadableStream({ + start(controller) { + controller.close(); + }, + }) + : ReadableStream.from(res._chunks); return { type: "core", statusCode, diff --git a/packages/open-next/src/http/openNextResponse.ts b/packages/open-next/src/http/openNextResponse.ts index 7e814856b..59f6b8eb8 100644 --- a/packages/open-next/src/http/openNextResponse.ts +++ b/packages/open-next/src/http/openNextResponse.ts @@ -282,6 +282,14 @@ export class OpenNextNodeResponse extends Transform implements ServerResponse { return Buffer.concat(this._chunks); } + getBodyLength(): number { + let size = 0; + for (const chunk of this._chunks) { + size += chunk.length; + } + return size; + } + private _internalWrite(chunk: any, encoding: BufferEncoding) { this._chunks.push(Buffer.from(chunk, encoding)); this.push(chunk, encoding); @@ -310,7 +318,7 @@ export class OpenNextNodeResponse extends Transform implements ServerResponse { globalThis.__openNextAls ?.getStore() ?.pendingPromiseRunner.add(this.onEnd(this.headers)); - const bodyLength = this.getBody().length; + const bodyLength = this.getBodyLength(); this.streamCreator?.onFinish?.(bodyLength); //This is only here because of aws broken streaming implementation. @@ -366,8 +374,10 @@ export class OpenNextNodeResponse extends Transform implements ServerResponse { } send() { - const body = this.getBody(); - this.end(body); + for (const chunk of this._chunks) { + this.write(chunk); + } + this.end(); } body(value: string) { diff --git a/packages/tests-unit/tests/adapters/cache.test.ts b/packages/tests-unit/tests/adapters/cache.test.ts index 82dcd7def..9560f97bd 100644 --- a/packages/tests-unit/tests/adapters/cache.test.ts +++ b/packages/tests-unit/tests/adapters/cache.test.ts @@ -515,9 +515,9 @@ describe("CacheHandler", () => { it("Should not throw when set cache throws", async () => { incrementalCache.set.mockRejectedValueOnce(new Error("Error")); - expect( - async () => await cache.set("key", { kind: "REDIRECT", props: {} }), - ).not.toThrow(); + await expect( + cache.set("key", { kind: "REDIRECT", props: {} }), + ).resolves.not.toThrow(); }); }); diff --git a/packages/tests-unit/tests/core/routing/util.test.ts b/packages/tests-unit/tests/core/routing/util.test.ts index bc6b62fd4..0b3a6169b 100644 --- a/packages/tests-unit/tests/core/routing/util.test.ts +++ b/packages/tests-unit/tests/core/routing/util.test.ts @@ -51,6 +51,7 @@ function createResponse(res: Partial) { getFixedHeaders: () => res.headers ?? {}, body: res.body ?? "", getBody: () => Buffer.from(res.body ?? ""), + _chunks: res.body ? [Buffer.from(res.body)] : [], }; } From b8353ac372c70afd61047d88108d7dffe42a8901 Mon Sep 17 00:00:00 2001 From: Victor Berchet Date: Wed, 8 Oct 2025 09:56:22 +0200 Subject: [PATCH 2/3] fixup: restore support for Node18 --- .github/actions/pnpm-setup/action.yml | 4 ++-- .github/workflows/e2e.yml | 6 +++--- .github/workflows/pre-release.yml | 2 +- .github/workflows/v2-release.yml | 2 +- packages/open-next/src/core/routing/util.ts | 16 ++++++++-------- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/.github/actions/pnpm-setup/action.yml b/.github/actions/pnpm-setup/action.yml index 13357de77..2355da8ef 100644 --- a/.github/actions/pnpm-setup/action.yml +++ b/.github/actions/pnpm-setup/action.yml @@ -6,9 +6,9 @@ runs: steps: # Install nodejs. https://github.com/actions/setup-node - name: Setup Node.js - uses: actions/setup-node@v5 + uses: actions/setup-node@v4 with: - node-version: 20.x + node-version: 18.x # Install pnpm. https://github.com/pnpm/action-setup - uses: pnpm/action-setup@v4 diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 4ac03e7a1..d903a6025 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -90,11 +90,11 @@ jobs: with: version: 9 - - name: Set up NodeJS v20 - uses: actions/setup-node@v5 + - name: Set up NodeJS v18 + uses: actions/setup-node@v4 with: cache: pnpm # cache pnpm store - node-version: 20.19.5 + node-version: 18.18.2 - name: Install packages run: pnpm install diff --git a/.github/workflows/pre-release.yml b/.github/workflows/pre-release.yml index 8d250ade4..96a56d054 100644 --- a/.github/workflows/pre-release.yml +++ b/.github/workflows/pre-release.yml @@ -20,7 +20,7 @@ jobs: uses: actions/checkout@v4 # Setup .npmrc file to publish to npm - - uses: actions/setup-node@v5 + - uses: actions/setup-node@v4 with: registry-url: "https://registry.npmjs.org" diff --git a/.github/workflows/v2-release.yml b/.github/workflows/v2-release.yml index 41550c0a5..a26e52811 100644 --- a/.github/workflows/v2-release.yml +++ b/.github/workflows/v2-release.yml @@ -12,7 +12,7 @@ jobs: uses: actions/checkout@v4 # Setup .npmrc file to publish to npm - - uses: actions/setup-node@v5 + - uses: actions/setup-node@v4 with: registry-url: "https://registry.npmjs.org" diff --git a/packages/open-next/src/core/routing/util.ts b/packages/open-next/src/core/routing/util.ts index e0d7c9fc8..703c9bddc 100644 --- a/packages/open-next/src/core/routing/util.ts +++ b/packages/open-next/src/core/routing/util.ts @@ -105,14 +105,14 @@ export function convertRes(res: OpenNextNodeResponse): InternalResult { const isBase64Encoded = isBinaryContentType(headers["content-type"]) || !!headers["content-encoding"]; - const body = - res._chunks == null - ? new ReadableStream({ - start(controller) { - controller.close(); - }, - }) - : ReadableStream.from(res._chunks); + const body = new ReadableStream({ + start(controller) { + for (const chunk of res._chunks ?? []) { + controller.enqueue(chunk); + } + controller.close(); + }, + }); return { type: "core", statusCode, From 2629191e213e060842be3896aee7ac14e8c64bfe Mon Sep 17 00:00:00 2001 From: Victor Berchet Date: Wed, 8 Oct 2025 12:34:33 +0200 Subject: [PATCH 3/3] fixup! pull --- packages/open-next/src/core/routing/util.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/open-next/src/core/routing/util.ts b/packages/open-next/src/core/routing/util.ts index 703c9bddc..abce39236 100644 --- a/packages/open-next/src/core/routing/util.ts +++ b/packages/open-next/src/core/routing/util.ts @@ -106,11 +106,13 @@ export function convertRes(res: OpenNextNodeResponse): InternalResult { isBinaryContentType(headers["content-type"]) || !!headers["content-encoding"]; const body = new ReadableStream({ - start(controller) { - for (const chunk of res._chunks ?? []) { - controller.enqueue(chunk); + pull(controller) { + if (!res._chunks || res._chunks.length === 0) { + controller.close(); + return; } - controller.close(); + + controller.enqueue(res._chunks.shift()); }, }); return {