diff --git a/docs/platforms/javascript/guides/cloudflare/frameworks/hydrogen-react-router.mdx b/docs/platforms/javascript/guides/cloudflare/frameworks/hydrogen-react-router.mdx new file mode 100644 index 0000000000000..3cb5e3a9c46e0 --- /dev/null +++ b/docs/platforms/javascript/guides/cloudflare/frameworks/hydrogen-react-router.mdx @@ -0,0 +1,202 @@ +--- +title: Hydrogen with React Router +description: Learn how to use the Sentry React Router SDK to instrument your Hydrogen app (versions 2025.5.0+). +--- + + + +This guide applies to Hydrogen versions **2025.5.0 and later** that use React Router 7 (framework mode). For older versions of Hydrogen that use Remix v2, see the [Remix guide](/platforms/javascript/guides/cloudflare/frameworks/hydrogen-remix/). + + + +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. + +## Installing Sentry React Router and Cloudflare SDKs + +First, install the Sentry React Router and Cloudflare SDKs with your package manager: + +```bash {tabTitle:npm} +npm install @sentry/react-router @sentry/cloudflare --save +``` + +```bash {tabTitle:yarn} +yarn add @sentry/react-router @sentry/cloudflare +``` + +```bash {tabTitle:pnpm} +pnpm add @sentry/react-router @sentry/cloudflare +``` + +## Instrumenting Your Server + +Create an `instrument.server.mjs` file to initialize Sentry on the server: + +```js {filename:instrument.server.mjs} +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`: + +```ts {filename:server.ts} +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 { + 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); + } + ); + }, +}; +``` + +## Instrumenting Your Client + +Initialize Sentry in your `entry.client.tsx` file: + +```tsx {filename:app/entry.client.tsx} +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: "___PUBLIC_DSN___", + integrations: [Sentry.reactRouterTracingIntegration()], + tracesSampleRate: 1.0, +}); + +startTransition(() => { + hydrateRoot( + document, + + + , + ); +}); +``` + +## Server-Side Rendering with Trace Injection + +To enable distributed tracing, wrap your `handleRequest` function in your `entry.server.tsx` file and inject trace meta tags: + +```tsx {filename:app/entry.server.tsx} +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( + , + { + 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); +``` + +## Configuration + +### Vite Configuration + +Add the Sentry plugin to your `vite.config.ts`: + +```ts {filename:vite.config.ts} +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 + ], +})); +``` + +### Build Configuration + +Add the `buildEnd` hook to your `react-router.config.ts`: + +```ts {filename:react-router.config.ts} +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; +``` diff --git a/docs/platforms/javascript/guides/cloudflare/frameworks/hydrogen.mdx b/docs/platforms/javascript/guides/cloudflare/frameworks/hydrogen-remix.mdx similarity index 60% rename from docs/platforms/javascript/guides/cloudflare/frameworks/hydrogen.mdx rename to docs/platforms/javascript/guides/cloudflare/frameworks/hydrogen-remix.mdx index 961863b28e748..13614e3c20bec 100644 --- a/docs/platforms/javascript/guides/cloudflare/frameworks/hydrogen.mdx +++ b/docs/platforms/javascript/guides/cloudflare/frameworks/hydrogen-remix.mdx @@ -1,11 +1,17 @@ --- -title: Hydrogen Guide -description: "Learn how to use the Sentry Remix SDK to instrument your Hydrogen app." +title: Hydrogen with Remix (Legacy) +description: Learn how to use the Sentry Remix SDK to instrument your Hydrogen app (versions before 2025.5.0). --- -If you're using the Shopify's Hydrogen framework, you can use the Sentry Remix SDK to add Sentry instrumentation to your app. + -## 1. Installing Sentry Remix and Clouflare SDKs +This guide applies to Hydrogen versions **before 2025.5.0** that use Remix v2. For newer versions of Hydrogen (2025.5.0+) that use React Router 7, see the [React Router guide](/platforms/javascript/guides/cloudflare/frameworks/hydrogen-react-router/). + + + +If you're using Shopify's Hydrogen framework with Remix v2, you can use the Sentry Remix SDK to add Sentry instrumentation to your app. + +## Installing Sentry Remix and Cloudflare SDKs First, install the Sentry Remix and Cloudflare SDKs with your package manager: @@ -21,13 +27,14 @@ yarn add @sentry/remix @sentry/cloudflare pnpm add @sentry/remix @sentry/cloudflare ``` -## 2. Instrumenting Your Server +## Instrumenting Your Server -Then update your `server.ts` file to use the `wrapRequestHandler` method: +Update your `server.ts` file to use the `wrapRequestHandler` method from `@sentry/cloudflare/request` and `instrumentBuild` from `@sentry/remix/cloudflare`: ```ts {filename:server.ts} import { wrapRequestHandler } from "@sentry/cloudflare/request"; import { instrumentBuild } from "@sentry/remix/cloudflare"; +import { createRequestHandler } from "@remix-run/cloudflare"; // Virtual entry point for the app import * as remixBuild from 'virtual:remix/server-build'; @@ -53,33 +60,45 @@ export default { async () => { // Instrument your server build with Sentry // and use the instrumented build inside the fetch handler - const instrumentedBuild = instrumentBuild(remixBuild) + const instrumentedBuild = instrumentBuild(remixBuild); + + const handleRequest = createRequestHandler({ + build: instrumentedBuild, + mode: process.env.NODE_ENV, + getLoadContext: (): AppLoadContext => ({ + // your load context + }), + }); - // request handling logic + return handleRequest(request); } ); }, }; ``` -## 3. Instrumenting Your Client +## Instrumenting Your Client Wrap your Remix root component using `withSentry`: -```tsx {filename:root.tsx} -import { withSentry } from "@sentry/remix/cloudflare"; +```tsx {filename:app/root.tsx} +import * as Sentry from "@sentry/remix/cloudflare"; +import { useEffect } from "react"; +import { useLocation, useMatches } from "@remix-run/react"; -function App({ children }) { - return <>{children}; +function App() { + return ( + // Your app content + ); } // Pass `useEffect`, `useLocation` and `useMatches` hooks to `withSentry` -export default withSentry(App, useEffect, useLocation, useMatches); +export default Sentry.withSentry(App, useEffect, useLocation, useMatches); ``` Finally, update your `entry.client.tsx` file to initialize Sentry SDK on the client: -```tsx {filename:entry.client.tsx} +```tsx {filename:app/entry.client.tsx} import * as Sentry from "@sentry/remix"; Sentry.init({