fix: silence streamed Suspense boundary error message in prod#482
Conversation
The page-level streaming path (streamingHtmlResponse) emitted the raw boundary error message in BOTH dev and prod, leaking internal detail (a DB error, a stack-derived path) to the client when a streamed Suspense boundary rejects. This contradicted the framework's own SSR error-isolation policy (render-server.js defaultSSRErrorTemplate is prod-silent, and the core renderToStream boundary path already uses it). Thread the dev flag into streamingHtmlResponse and gate the message: dev surfaces it for debugging, prod emits an empty (isolated) boundary.
vivek7405
left a comment
There was a problem hiding this comment.
Went back and forth on the signal here. dev is the right one for this path: the deployment model keys prod-silence on the CLI dev flag, not NODE_ENV, so this actually closes the leak under webjs start even when NODE_ENV was never exported. What it does NOT close: the per-component isolation (defaultSSRErrorTemplate) still gates on NODE_ENV, so a bare webjs start with NODE_ENV unset leaks a component error while this boundary path stays silent. The two only line up when NODE_ENV=production is set, which the Dockerfile does but a bare start does not. That is a separate, pre-existing surface, so I filed #483 to align every SSR error path on the dev flag rather than widen this PR. The boundary fix and its prod/dev test stand.
The @param list had drifted (param 1 documented as headHtml but it is prefix; closer was undocumented). Correct the names and add the missing entry so the list matches (prefix, bodyHtml, closer, ...nonce, dev).
vivek7405
left a comment
There was a problem hiding this comment.
Second pass, focused on the test and the plumbing. The test is solid (it asserts the fallback flushed and the content rendered, so an empty body can't false-pass the prod case, and the rejection is awaited so no unhandled-rejection flake), and opts.dev is a real boolean at the call site, so no accidental-falsy trap. One small thing on the JSDoc I was already touching.
vivek7405
left a comment
There was a problem hiding this comment.
Final pass is clean. The boundary fix sits on the right signal, the JSDoc lines up with the signature now, and the test holds the prod/dev split with a working counterfactual. Ready to merge; the broader component-path alignment is tracked in #483.
Summary
Closes #478.
The page-level streaming path (
ssr.jsstreamingHtmlResponse) emitted the raw boundary error message in BOTH dev and prod when a streamedSuspenseboundary rejects, leaking internal detail (a DB error, a stack-derived path) to the client. The fix threads thedevflag intostreamingHtmlResponseand gates the placeholder: dev surfaceserror: <msg>for debugging, prod emits an empty (isolated) boundary.devis webjs's authoritative prod signal: the deployment model uses the CLIdevflag, notNODE_ENV(docs/app/docs/deployment/page.ts), and the Dockerfile setsNODE_ENV=productionfor app code / deps but the server itself keys ondev. So underwebjs start(dev: false) this is silent regardless of whetherNODE_ENVwas exported.Pre-existing on main (not a regression from #470); narrow to trigger (per-component async-render throws are already isolated, so this catch only fires on a top-level boundary rejection or a non-isolated
renderToStringthrow).Scope note: this fixes the page-streaming BOUNDARY path. The per-component SSR isolation (
render-server.jsdefaultSSRErrorTemplate) and the corerenderToStreamboundary catch still key onNODE_ENV(isProd()), so under a barewebjs startwithNODE_ENVunset those paths still leak a component error. That broader inconsistency (align all SSR error paths on thedevflag) is tracked separately as #483.Test plan
packages/server/test/dev/streaming-error-isolation.test.js): a fixture page whose top-levelSuspenseboundary rejects with a recognizable secret, driven throughcreateRequestHandler. PROD asserts the body does NOT contain the message (still 200, fallback flushed, content rendered); DEV asserts it DOES. Counterfactual verified: neutering the gate fails the PROD case.dev/+test/ssr/suites: 346 pass.Definition of done
dev-vs-NODE_ENVinconsistency with the component path) is filed as fix: SSR error prod-silence keys on NODE_ENV not the dev flag, leaks under bare webjs start #483, since aligning the component-isolation path is a separate pre-existing surface.