You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
feat(react-router): support renderToReadableStream in createSentryHandleRequest #25168
createSentryHandleRequest() only supports the Node streaming path. It requires renderToPipeableStream and createReadableStreamFromReadable from @react-router/node, writes into a Node PassThrough, and injects trace meta tags with a Node Transform (getMetaTagTransformer).
React Router now defaults non-Node runtimes to renderToReadableStream, and Node 22+ apps can opt into Web Streams with future.unstable_enableNodeReadableStream. Apps on that path that also want Sentry's route parametrization and trace meta-tag injection have to copy the handler and compose getMetaTagTransformer() and wrapSentryHandleRequest() by hand. This is the same workaround described in #23445.
Accept a renderToReadableStream option as an alternative to renderToPipeableStream, and keep the same behavior:
route parametrization via wrapSentryHandleRequest (unchanged)
onAllReady for bots and SPA mode, onShellReady otherwise
a timeout that aborts rendering (equivalent to streamTimeout, via the render signal)
status 500 when rendering errors occur
trace meta tags injected before </head> with a Web TransformStream
Note for the implementation: injectTraceMetaTags in @sentry/react-router/cloudflare (added in #17145) matches </head> per chunk and decodes each chunk independently. A tag split across chunks is not injected, and a multi-byte character split across chunks can be corrupted. A Web-stream version should buffer enough to handle both cases.
Hey @lfantone thanks for the detailed write-up! This generally makes sense to me, feel free to open a pr!
Some constraints so it stays non-breaking:
Keep SentryHandleRequestOptions as is. Please don't make renderToPipeableStream or createReadableStreamFromReadable optional, and don't turn the interface into a union. Add a separate options type for the Web Streams path and accept it through an overload of createSentryHandleRequest. Existing setups should type-check and behave exactly as before.
Leave the Node path unchanged. Only switch to the new code when renderToReadableStream is passed in.
No new runtime imports or peer dependency changes. renderToReadableStream should be passed in by the caller, the same way renderToPipeableStream is today.
Put the Web Streams handler in its own module with no Node imports, and export it from the cloudflare entry as well as the server entry. Right now the server entry imports stream, and the workerd/worker builds don't export createSentryHandleRequest at all, so otherwise it would only help Node apps.
Handle </head> split across chunks. The split multi-byte character case is already fixed, since both transformers now use a single streaming TextDecoder. A </head> split across two chunks is still missed, though. Holding back the last few characters of each chunk until the next one arrives should be enough. If you do that in a shared helper, please apply it to the Node transformer too.
For tests: unit tests that cover the bot/SPA allReady path, the timeout abort, the 500 status on render errors, and the split </head> case. If you add e2e coverage please do so in an existing app.
Activity
linear-code commentedon Oct 8, 2026
JS-3891
chargome commentedon Oct 8, 2026
Hey @lfantone thanks for the detailed write-up! This generally makes sense to me, feel free to open a pr!
Some constraints so it stays non-breaking:
SentryHandleRequestOptionsas is. Please don't makerenderToPipeableStreamorcreateReadableStreamFromReadableoptional, and don't turn the interface into a union. Add a separate options type for the Web Streams path and accept it through an overload ofcreateSentryHandleRequest. Existing setups should type-check and behave exactly as before.renderToReadableStreamis passed in.renderToReadableStreamshould be passed in by the caller, the same wayrenderToPipeableStreamis today.createSentryHandleRequestat all, so otherwise it would only help Node apps.</head>split across chunks. The split multi-byte character case is already fixed, since both transformers now use a single streamingTextDecoder. A</head>split across two chunks is still missed, though. Holding back the last few characters of each chunk until the next one arrives should be enough. If you do that in a shared helper, please apply it to the Node transformer too.For tests: unit tests that cover the bot/SPA allReady path, the timeout abort, the 500 status on render errors, and the split
</head>case. If you add e2e coverage please do so in an existing app.lfantone commentedon Oct 12, 2026
Opened a draft PR following the constraints above: #25254
feat(react-router): Support renderToReadableStream in createSentryHan…