nextjs - 💡(How to fix) Fix errorsRscStreamsByHtmlRequestId: deleteErrorsRscStreamForHtmlRequest exported but never called

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…

packages/next/src/server/dev/serialized-errors.ts exports deleteErrorsRscStreamForHtmlRequest, but no file in the repository ever imports or calls it. The module-level errorsRscStreamsByHtmlRequestId Map is populated via setErrorsRscStreamForHtmlRequest when an HTML request's errors stream arrives before its WebSocket client connects. Once the client does connect, sendSerializedErrorsToClientForHtmlRequest drains the entry. However, if the client disconnects before connecting to the HMR WebSocket (e.g. a curl request, a page load with JavaScript disabled, or a navigation abort), the entry is never removed. The existing client.on('close', ...) handlers in both hot-reloaders already clean up the analogous ReactDebugChannel entry via deleteReactDebugChannelForHtmlRequest, but the parallel call for the errors stream is absent. The file itself acknowledges this with a // TODO: Clean up after a timeout, in case the client never connects comment inside setErrorsRscStreamForHtmlRequest.

Root Cause

packages/next/src/server/dev/serialized-errors.ts exports deleteErrorsRscStreamForHtmlRequest, but no file in the repository ever imports or calls it. The module-level errorsRscStreamsByHtmlRequestId Map is populated via setErrorsRscStreamForHtmlRequest when an HTML request's errors stream arrives before its WebSocket client connects. Once the client does connect, sendSerializedErrorsToClientForHtmlRequest drains the entry. However, if the client disconnects before connecting to the HMR WebSocket (e.g. a curl request, a page load with JavaScript disabled, or a navigation abort), the entry is never removed. The existing client.on('close', ...) handlers in both hot-reloaders already clean up the analogous ReactDebugChannel entry via deleteReactDebugChannelForHtmlRequest, but the parallel call for the errors stream is absent. The file itself acknowledges this with a // TODO: Clean up after a timeout, in case the client never connects comment inside setErrorsRscStreamForHtmlRequest.

Code Example

const errorsRscStreamsByHtmlRequestId = new Map<string, AnyStream>()

export function setErrorsRscStreamForHtmlRequest(
  htmlRequestId: string,
  errorsRscStream: AnyStream
) {
  // TODO: Clean up after a timeout, in case the client never connects, e.g.
  // when CURL'ing the page, or loading the page with JavaScript disabled etc.
  errorsRscStreamsByHtmlRequestId.set(htmlRequestId, errorsRscStream)
}

export function deleteErrorsRscStreamForHtmlRequest(htmlRequestId: string) {
  errorsRscStreamsByHtmlRequestId.delete(htmlRequestId)
}

---

client.on('close', () => {
  this.webpackHotMiddleware?.deleteClient(client, htmlRequestId)

  if (htmlRequestId) {
    deleteReactDebugChannelForHtmlRequest(htmlRequestId)
    // deleteErrorsRscStreamForHtmlRequest is NOT called here
  }
})

---

client.on('close', () => {
  for (const subscription of subscriptions.values()) {
    subscription.return?.()
  }
  clientStates.delete(client)

  if (htmlRequestId) {
    clientsByHtmlRequestId.delete(htmlRequestId)
    deleteReactDebugChannelForHtmlRequest(htmlRequestId)
    // deleteErrorsRscStreamForHtmlRequest is NOT called here
  } else {
    clientsWithoutHtmlRequestId.delete(client)
  }
})

---

import {
   sendSerializedErrorsToClient,
   sendSerializedErrorsToClientForHtmlRequest,
+  deleteErrorsRscStreamForHtmlRequest,
   setErrorsRscStreamForHtmlRequest,
 } from './serialized-errors'

 // ...

       client.on('close', () => {
         this.webpackHotMiddleware?.deleteClient(client, htmlRequestId)

         if (htmlRequestId) {
           deleteReactDebugChannelForHtmlRequest(htmlRequestId)
+          deleteErrorsRscStreamForHtmlRequest(htmlRequestId)
         }
       })

---

clientsByHtmlRequestId.delete(htmlRequestId)
           deleteReactDebugChannelForHtmlRequest(htmlRequestId)
+          deleteErrorsRscStreamForHtmlRequest(htmlRequestId)
RAW_BUFFERClick to expand / collapse

Summary

packages/next/src/server/dev/serialized-errors.ts exports deleteErrorsRscStreamForHtmlRequest, but no file in the repository ever imports or calls it. The module-level errorsRscStreamsByHtmlRequestId Map is populated via setErrorsRscStreamForHtmlRequest when an HTML request's errors stream arrives before its WebSocket client connects. Once the client does connect, sendSerializedErrorsToClientForHtmlRequest drains the entry. However, if the client disconnects before connecting to the HMR WebSocket (e.g. a curl request, a page load with JavaScript disabled, or a navigation abort), the entry is never removed. The existing client.on('close', ...) handlers in both hot-reloaders already clean up the analogous ReactDebugChannel entry via deleteReactDebugChannelForHtmlRequest, but the parallel call for the errors stream is absent. The file itself acknowledges this with a // TODO: Clean up after a timeout, in case the client never connects comment inside setErrorsRscStreamForHtmlRequest.

What I observed

packages/next/src/server/dev/serialized-errors.ts (lines 8–53)

const errorsRscStreamsByHtmlRequestId = new Map<string, AnyStream>()

export function setErrorsRscStreamForHtmlRequest(
  htmlRequestId: string,
  errorsRscStream: AnyStream
) {
  // TODO: Clean up after a timeout, in case the client never connects, e.g.
  // when CURL'ing the page, or loading the page with JavaScript disabled etc.
  errorsRscStreamsByHtmlRequestId.set(htmlRequestId, errorsRscStream)
}

export function deleteErrorsRscStreamForHtmlRequest(htmlRequestId: string) {
  errorsRscStreamsByHtmlRequestId.delete(htmlRequestId)
}

deleteErrorsRscStreamForHtmlRequest is the only export in this file that has zero callers across the entire repository (gh api search/code returns only the definition site).

packages/next/src/server/dev/hot-reloader-webpack.tsclient.on('close', ...) handler

client.on('close', () => {
  this.webpackHotMiddleware?.deleteClient(client, htmlRequestId)

  if (htmlRequestId) {
    deleteReactDebugChannelForHtmlRequest(htmlRequestId)
    // deleteErrorsRscStreamForHtmlRequest is NOT called here
  }
})

packages/next/src/server/dev/hot-reloader-turbopack.tsclient.on('close', ...) handler

client.on('close', () => {
  for (const subscription of subscriptions.values()) {
    subscription.return?.()
  }
  clientStates.delete(client)

  if (htmlRequestId) {
    clientsByHtmlRequestId.delete(htmlRequestId)
    deleteReactDebugChannelForHtmlRequest(htmlRequestId)
    // deleteErrorsRscStreamForHtmlRequest is NOT called here
  } else {
    clientsWithoutHtmlRequestId.delete(client)
  }
})

Both hot-reloaders already import from serialized-errors, but only import sendSerializedErrorsToClient, sendSerializedErrorsToClientForHtmlRequest, and setErrorsRscStreamForHtmlRequest — not deleteErrorsRscStreamForHtmlRequest.

Suggested fix

Add deleteErrorsRscStreamForHtmlRequest to the import and invoke it in both close handlers, mirroring the existing deleteReactDebugChannelForHtmlRequest call.

hot-reloader-webpack.ts

 import {
   sendSerializedErrorsToClient,
   sendSerializedErrorsToClientForHtmlRequest,
+  deleteErrorsRscStreamForHtmlRequest,
   setErrorsRscStreamForHtmlRequest,
 } from './serialized-errors'

 // ...

       client.on('close', () => {
         this.webpackHotMiddleware?.deleteClient(client, htmlRequestId)

         if (htmlRequestId) {
           deleteReactDebugChannelForHtmlRequest(htmlRequestId)
+          deleteErrorsRscStreamForHtmlRequest(htmlRequestId)
         }
       })

hot-reloader-turbopack.ts — same import addition, and inside the if (htmlRequestId) block of the close handler:

           clientsByHtmlRequestId.delete(htmlRequestId)
           deleteReactDebugChannelForHtmlRequest(htmlRequestId)
+          deleteErrorsRscStreamForHtmlRequest(htmlRequestId)

Notes

The // TODO comment in setErrorsRscStreamForHtmlRequest hints at a second gap: requests that are never followed by a WebSocket connection at all (curl, JS-disabled browsers). A timeout-based fallback would cover that case, but the close-handler wiring above is the minimum fix for the normal disconnect path and is what deleteErrorsRscStreamForHtmlRequest was clearly written for.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING