Skip to content

rscOnError silently swallows Server Component errors in production — reportRequestError is a no-op #347

@Jbithell

Description

@Jbithell

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions