Hydrogen with React Router

Learn how to use the Sentry React Router SDK to instrument your Hydrogen app (versions 2025.5.0+).

Starting from Hydrogen version 2025.5.0, Shopify switched from Remix v2 to React Router 7 (framework mode). You can use the Sentry React Router SDK with Cloudflare support to add Sentry instrumentation to your Hydrogen app.

First, install the Sentry React Router and Cloudflare SDKs with your package manager:

Copied
npm install @sentry/react-router @sentry/cloudflare --save

Create an instrument.server.mjs file to initialize Sentry on the server:

instrument.server.mjs
Copied
import * as Sentry from "@sentry/react-router";

Sentry.init({
  dsn: "YOUR_DSN_HERE",
  // Adds request headers and IP for users, for more info visit:
  // https://docs.sentry.io/platforms/javascript/guides/react-router/configuration/options/#sendDefaultPii
  sendDefaultPii: true,
  tracesSampleRate: 1.0,
});

Update your server.ts file to use the wrapRequestHandler method from @sentry/cloudflare:

server.ts
Copied
import { wrapRequestHandler } from '@sentry/cloudflare';
// ...other imports

/**
 * Export a fetch handler in module format.
 */
export default {
  async fetch(
    request: Request,
    env: Env,
    executionContext: ExecutionContext
  ): Promise<Response> {
    return wrapRequestHandler(
      {
        options: {
          dsn: "YOUR_DSN_HERE",
          tracesSampleRate: 1.0,
        },
        request: request as any,
        context: executionContext,
      },
      async () => {
        // Your existing Hydrogen server logic
        const handleRequest = createRequestHandler({
          // @ts-ignore
          build: await import('virtual:react-router/server-build'),
          mode: process.env.NODE_ENV,
          getLoadContext: (): AppLoadContext => ({
            // your load context
          }),
        });

        return handleRequest(request);
      }
    );
  },
};

Initialize Sentry in your entry.client.tsx file:

app/entry.client.tsx
Copied
import { HydratedRouter } from "react-router/dom";
import * as Sentry from "@sentry/react-router/cloudflare";
import { StrictMode, startTransition } from "react";
import { hydrateRoot } from "react-dom/client";

Sentry.init({
  dsn: "https://examplePublicKey@o0.ingest.sentry.io/0",
  integrations: [Sentry.reactRouterTracingIntegration()],
  tracesSampleRate: 1.0,
});

startTransition(() => {
  hydrateRoot(
    document,
    <StrictMode>
      <HydratedRouter />
    </StrictMode>,
  );
});

To enable distributed tracing, wrap your handleRequest function in your entry.server.tsx file and inject trace meta tags:

app/entry.server.tsx
Copied
import "./instrument.server";
import { HandleErrorFunction, ServerRouter } from "react-router";
import type { EntryContext } from "@shopify/remix-oxygen";
import { renderToReadableStream } from "react-dom/server";
import * as Sentry from "@sentry/react-router/cloudflare";

async function handleRequest(
  request: Request,
  responseStatusCode: number,
  responseHeaders: Headers,
  reactRouterContext: EntryContext,
) {
  const body = Sentry.injectTraceMetaTags(
    await renderToReadableStream(
      <ServerRouter context={reactRouterContext} url={request.url} />,
      {
        signal: request.signal,
      },
    ),
  );

  responseHeaders.set("Content-Type", "text/html");

  return new Response(body, {
    headers: responseHeaders,
    status: responseStatusCode,
  });
}

export const handleError: HandleErrorFunction = (error, { request }) => {
  // React Router may abort some interrupted requests, don't log those
  if (!request.signal.aborted) {
    Sentry.captureException(error);
    console.error(error);
  }
};

export default Sentry.wrapSentryHandleRequest(handleRequest);

Add the Sentry plugin to your vite.config.ts:

vite.config.ts
Copied
import { reactRouter } from '@react-router/dev/vite';
import { hydrogen } from '@shopify/hydrogen/vite';
import { oxygen } from '@shopify/mini-oxygen/vite';
import { defineConfig } from 'vite';
import { sentryReactRouter } from '@sentry/react-router';

export default defineConfig(config => ({
  plugins: [
    hydrogen(),
    oxygen(),
    reactRouter(),
    sentryReactRouter({
      org: "your-org-slug",
      project: "your-project-slug",
      authToken: process.env.SENTRY_AUTH_TOKEN,
    }, config),
    // ... other plugins
  ],
}));

Add the buildEnd hook to your react-router.config.ts:

react-router.config.ts
Copied
import type {Config} from '@react-router/dev/config';
import { sentryOnBuildEnd } from '@sentry/react-router';

export default {
  appDirectory: 'app',
  buildDirectory: 'dist',
  ssr: true,
  buildEnd: async ({ viteConfig, reactRouterConfig, buildManifest }) => {
    // Call this at the end of the hook
    (await sentryOnBuildEnd({ viteConfig, reactRouterConfig, buildManifest }));
  }
} satisfies Config;
Was this helpful?
Help improve this content
Our documentation is open source and available on GitHub. Your contributions are welcome, whether fixing a typo (drat!) or suggesting an update ("yeah, this would be better").