-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Closed
Labels
Description
Description
Deploying @sentry/react-router
to Cloudflare Workers is currently not supported out of the box. However, this issue provides a workaround for making it work.
Support renderToReadableStream
The react-router SDK is currently using renderToPipeableStream
which is a Node-only API. For react-router to be able to inject the HTML meta tags, this code needs to be adapted to use renderToReadableStream
.
Current Workaround
Wrap the handler with withSentry
from @sentry/cloudflare
:
// in entry.worker.ts
import type { unstable_InitialContext } from "react-router";
import { createRequestHandler, unstable_createContext } from "react-router";
import * as build from "virtual:react-router/server-build";
import * as Sentry from "@sentry/cloudflare";
const CloudflareContext = unstable_createContext<{
env: Cloudflare.Env;
ctx: ExecutionContext;
cf?: RequestInitCfProperties;
}>();
export default Sentry.withSentry(
() => ({ /* Sentry config options */ }),
{
async fetch(request: Request, env: Cloudflare.Env, ctx: ExecutionContext) {
const context: unstable_InitialContext = new Map();
context.set(CloudflareContext, { env, ctx, cf: request.cf });
return await handler(request, context);
},
} satisfies ExportedHandler<Cloudflare.Env>,
);
Now, create a function to include the HTML meta tags (like here) and inject them into the returned body:
// in app/entry.server.tsx
import { renderToReadableStream } from "react-dom/server";
import { getTraceMetaTags, getTraceData, getClient } from "@sentry/core";
function getWebMetaTagTransformer(): TransformStream<Uint8Array, Uint8Array> {
const headClosingTag = "</head>";
const decoder = new TextDecoder();
const encoder = new TextEncoder();
return new TransformStream({
transform(chunk, controller) {
const html = decoder.decode(chunk);
if (html.includes(headClosingTag)) {
const modifiedHtml = html.replace(
headClosingTag,
`${getTraceMetaTags()}${headClosingTag}`,
);
controller.enqueue(encoder.encode(modifiedHtml));
return;
}
controller.enqueue(chunk);
},
});
}
export default async function handleRequest(/* .... */){
// ...
const body = await renderToReadableStream(/* ... */)
const sentryTransformer = getWebMetaTagTransformer();
const transformedBody = body.pipeThrough(sentryTransformer);
return new Response(transformedBody, {
headers: responseHeaders,
status: responseStatusCode,
});
}