Skip to content

[React-Router Framework]: Add Cloudflare support #17334

@s1gr1d

Description

@s1gr1d

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,
   });
}

Metadata

Metadata

Assignees

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions