[GH-ISSUE #347] rscOnError silently swallows Server Component errors in production — reportRequestError is a no-op #80

Closed
opened 2026-05-06 12:37:03 +02:00 by BreizhHardware · 0 comments

Originally created by @Jbithell on GitHub (Mar 8, 2026).
Original GitHub issue: https://github.com/cloudflare/vinext/issues/347

Summary

In production builds, Server Component render errors are silently swallowed. rscOnError generates a digest but never logs or reports the original error, and reportRequestError is a no-op in the production entry point. This makes it impossible for error-tracking services (e.g. Sentry) to capture the real error — they only see the sanitised message React sends to the client:

"An error occurred in the Server Components render. The specific message is omitted in production builds to avoid leaking sensitive details."

Details

rscOnError doesn't log errors

In the production bundle, rscOnError only generates a digest hash and returns it. For Error instances it never calls console.error, reportRequestError, or any other reporting mechanism:

// From the built output (dist/server/assets/worker-entry-*.js)
function rscOnError(error2) {
  if (error2 && typeof error2 === "object" && "digest" in error2) {
    return String(error2.digest);
  }
  if (error2) {
    const msg = error2 instanceof Error ? error2.message : String(error2);
    const stack = error2 instanceof Error ? error2.stack || "" : "";
    return __errorDigest(msg + stack);
  }
  return void 0;
}

Compare this to the dev server (app-dev-server.ts) which calls _reportRequestError(error) for server actions — but even there, rscOnError itself doesn't report errors.

reportRequestError is a no-op in production

The production entry point (prod-server.js) contains:

async function reportRequestError(error2, request, context) {
  return;
}

Even if rscOnError were to call reportRequestError, it would do nothing. The instrumentation.ts onRequestError hook is loaded but never wired into the production code path.

Expected behaviour

  1. rscOnError should call console.error(error) (like React's own SSR renderer does) and/or call reportRequestError so the original error is observable
  2. reportRequestError should actually invoke onRequestError from instrumentation.ts in production builds, not be a no-op
  3. The onRequestError instrumentation hook should be wired into the production entry point

Workaround

We're using a Vite renderChunk plugin to patch rscOnError in the final bundle to inject console.error(error), combined with Sentry's captureConsoleIntegration to capture those as events:

function vinextRscErrorReporting(): Plugin {
  return {
    name: "vinext-rsc-error-reporting",
    renderChunk(code) {
      if (!code.includes("function rscOnError(")) return;
      const patched = code.replace(
        /function rscOnError\((\w+)\)\s*\{([\s\S]*?)return __errorDigest\((\w+) \+ (\w+)\);/,
        (match, errorVar, before, msgVar, stackVar) =>
          `function rscOnError(${errorVar}) {${before}` +
          `console.error(${errorVar});\n` +
          `    return __errorDigest(${msgVar} + ${stackVar});`,
      );
      if (patched !== code) return { code: patched, map: null };
    },
  };
}

This is fragile and will break if vinext's internal code generation changes

Originally created by @Jbithell on GitHub (Mar 8, 2026). Original GitHub issue: https://github.com/cloudflare/vinext/issues/347 ## Summary In production builds, Server Component render errors are silently swallowed. `rscOnError` generates a digest but never logs or reports the original error, and `reportRequestError` is a no-op in the production entry point. This makes it impossible for error-tracking services (e.g. Sentry) to capture the real error — they only see the sanitised message React sends to the client: > "An error occurred in the Server Components render. The specific message is omitted in production builds to avoid leaking sensitive details." ## Details ### `rscOnError` doesn't log errors In the production bundle, `rscOnError` only generates a digest hash and returns it. For `Error` instances it never calls `console.error`, `reportRequestError`, or any other reporting mechanism: ```js // From the built output (dist/server/assets/worker-entry-*.js) function rscOnError(error2) { if (error2 && typeof error2 === "object" && "digest" in error2) { return String(error2.digest); } if (error2) { const msg = error2 instanceof Error ? error2.message : String(error2); const stack = error2 instanceof Error ? error2.stack || "" : ""; return __errorDigest(msg + stack); } return void 0; } ``` Compare this to the dev server (`app-dev-server.ts`) which calls `_reportRequestError(error)` for server actions — but even there, `rscOnError` itself doesn't report errors. ### `reportRequestError` is a no-op in production The production entry point (`prod-server.js`) contains: ```js async function reportRequestError(error2, request, context) { return; } ``` Even if `rscOnError` were to call `reportRequestError`, it would do nothing. The `instrumentation.ts` `onRequestError` hook is loaded but never wired into the production code path. ## Expected behaviour 1. `rscOnError` should call `console.error(error)` (like React's own SSR renderer does) and/or call `reportRequestError` so the original error is observable 2. `reportRequestError` should actually invoke `onRequestError` from `instrumentation.ts` in production builds, not be a no-op 3. The `onRequestError` instrumentation hook should be wired into the production entry point ## Workaround We're using a Vite `renderChunk` plugin to patch `rscOnError` in the final bundle to inject `console.error(error)`, combined with Sentry's `captureConsoleIntegration` to capture those as events: ```ts function vinextRscErrorReporting(): Plugin { return { name: "vinext-rsc-error-reporting", renderChunk(code) { if (!code.includes("function rscOnError(")) return; const patched = code.replace( /function rscOnError\((\w+)\)\s*\{([\s\S]*?)return __errorDigest\((\w+) \+ (\w+)\);/, (match, errorVar, before, msgVar, stackVar) => `function rscOnError(${errorVar}) {${before}` + `console.error(${errorVar});\n` + ` return __errorDigest(${msgVar} + ${stackVar});`, ); if (patched !== code) return { code: patched, map: null }; }, }; } ``` This is fragile and will break if vinext's internal code generation changes
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/vinext#80
No description provided.