diff --git a/packages/react-router-dev/vite/node-adapter.ts b/packages/react-router-dev/vite/node-adapter.ts index 1c53cf42dd..45fbcae0ce 100644 --- a/packages/react-router-dev/vite/node-adapter.ts +++ b/packages/react-router-dev/vite/node-adapter.ts @@ -47,15 +47,20 @@ export function fromNodeRequest( let url = new URL(nodeReq.originalUrl, origin); // Abort action/loaders once we can no longer write a response - let controller = new AbortController(); - nodeRes.on("close", () => controller.abort()); - + let controller: AbortController | null = new AbortController(); let init: RequestInit = { method: nodeReq.method, headers: fromNodeHeaders(nodeReq.headers), signal: controller.signal, }; + // Abort action/loaders once we can no longer write a response iff we have + // not yet sent a response (i.e., `close` without `finish`) + // `finish` -> done rendering the response + // `close` -> response can no longer be written to + nodeRes.on("finish", () => (controller = null)); + nodeRes.on("close", () => controller?.abort()); + if (nodeReq.method !== "GET" && nodeReq.method !== "HEAD") { init.body = createReadableStreamFromReadable(nodeReq); (init as { duplex: "half" }).duplex = "half"; diff --git a/packages/react-router-express/server.ts b/packages/react-router-express/server.ts index 2b339e3799..6ff7e5387e 100644 --- a/packages/react-router-express/server.ts +++ b/packages/react-router-express/server.ts @@ -98,15 +98,20 @@ export function createRemixRequest( let url = new URL(`${req.protocol}://${resolvedHost}${req.originalUrl}`); // Abort action/loaders once we can no longer write a response - let controller = new AbortController(); - res.on("close", () => controller.abort()); - + let controller: AbortController | null = new AbortController(); let init: RequestInit = { method: req.method, headers: createRemixHeaders(req.headers), signal: controller.signal, }; + // Abort action/loaders once we can no longer write a response iff we have + // not yet sent a response (i.e., `close` without `finish`) + // `finish` -> done rendering the response + // `close` -> response can no longer be written to + res.on("finish", () => (controller = null)); + res.on("close", () => controller?.abort()); + if (req.method !== "GET" && req.method !== "HEAD") { init.body = createReadableStreamFromReadable(req); (init as { duplex: "half" }).duplex = "half";