Manual Setup

Learn how to set up the SDK manually.

If you can't (or prefer not to) run the automatic setup, you can follow the instructions below to configure your application.

Get started by installing the Sentry Remix SDK:

Copied
npm install --save @sentry/remix

To use this SDK, initialize Sentry in your Remix entry points for both the client and server.

Create two files in the root directory of your project, entry.client.tsx and entry.server.tsx (if they don't already exist). Add your initialization code in these files for the client-side and server-side SDK, respectively.

The two configuration types are mostly the same, except that some configuration features, like Session Replay, only work in entry.client.tsx.

entry.client.tsx
Copied
import { useLocation, useMatches } from "@remix-run/react";
import * as Sentry from "@sentry/remix";
import { useEffect } from "react";

Sentry.init({
  dsn: "https://examplePublicKey@o0.ingest.sentry.io/0",
  integrations: [
    Sentry.browserTracingIntegration({
      useEffect,
      useLocation,
      useMatches,
    }),
    // Replay is only available in the client
    Sentry.replayIntegration(),
  ],

  // Set tracesSampleRate to 1.0 to capture 100%
  // of transactions for tracing.
  // We recommend adjusting this value in production
  tracesSampleRate: 1.0,

  // Set `tracePropagationTargets` to control for which URLs distributed tracing should be enabled
  tracePropagationTargets: ["localhost", /^https:\/\/yourserver\.io\/api/],

  // Capture Replay for 10% of all sessions,
  // plus for 100% of sessions with an error
  replaysSessionSampleRate: 0.1,
  replaysOnErrorSampleRate: 1.0,
});

entry.server.tsx
Copied
import * as Sentry from "@sentry/remix";

Sentry.init({
  dsn: "https://examplePublicKey@o0.ingest.sentry.io/0",

  // Set tracesSampleRate to 1.0 to capture 100%
  // of transactions for tracing.
  // We recommend adjusting this value in production
  tracesSampleRate: 1.0,
});

Initialize Sentry in your entry point for the server to capture exceptions and get performance metrics for your action and loader functions. You can also initialize Sentry's database integrations, such as Prisma, to get spans for your database calls.

To catch React component errors (in Remix v1) and routing transactions (in all Remix versions), wrap your Remix root with withSentry.

If you use the Remix v2_errorBoundary future flag, you must also configure a v2 ErrorBoundary.

root.tsx
Copied
import {
  Links,
  LiveReload,
  Meta,
  Outlet,
  Scripts,
  ScrollRestoration,
} from "@remix-run/react";

import { withSentry } from "@sentry/remix";

function App() {
  return (
    <html>
      <head>
        <Meta />
        <Links />
      </head>
      <body>
        <Outlet />
        <ScrollRestoration />
        <Scripts />
        <LiveReload />
      </body>
    </html>
  );
}

export default withSentry(App);

If you use Remix v2, wrapWithErrorBoundary is disabled by default. You still need to wrap your root component with withSentry to capture routing transactions.

You can disable or configure ErrorBoundary using a second parameter to withSentry.

Copied
withSentry(App, {
  wrapWithErrorBoundary: false,
});

// or

withSentry(App, {
  errorBoundaryOptions: {
    fallback: <p>An error has occurred</p>,
  },
});

Available from SDK version 7.59.0

Remix v2 will introduce new features that require additional configuration to work with Sentry. These features are also available from version 1.17.0 with future flags.

To capture errors from v2 ErrorBoundary, you should define your own ErrorBoundary in root.tsx and use Sentry.captureRemixErrorBoundaryError inside of it. You can also create route-specific error capturing behavior by defining ErrorBoundary in your route components. The ErrorBoundary you define in root.tsx will be used as a fallback for all routes.

root.tsx
Copied
import { captureRemixErrorBoundaryError } from "@sentry/remix";

export const ErrorBoundary: V2_ErrorBoundaryComponent = () => {
  const error = useRouteError();

  captureRemixErrorBoundaryError(error);

  return <div> ... </div>;
};

Sentry won't be able to capture your unexpected server-side errors automatically on Remix v2. To work around this, instrument the handleError function in your server entry point.

If you're using Sentry Remix SDK version 7.87.0 or higher, you can use wrapHandleErrorWithSentry to export as your handleError function.

entry.server.tsx (@sentry/remix >= 7.87.0)
Copied
import { wrapHandleErrorWithSentry } from "@sentry/remix";

export const handleError = wrapHandleErrorWithSentry;

For SDK versions older than 7.87.0, you can use Sentry.captureRemixServerException to capture errors inside handleError.

entry.server.tsx (@sentry/remix < 7.87.0)
Copied
export function handleError(
  error: unknown,
  { request }: DataFunctionArgs,
): void {
  if (error instanceof Error) {
    Sentry.captureRemixServerException(error, "remix.server", request);
  } else {
    // Optionally capture non-Error objects
    Sentry.captureException(error);
  }
}

After you've completed this setup, the SDK will automatically capture unhandled errors and promise rejections, and monitor performance in the client. You can also manually capture errors.

You can refer to Remix Docs to learn how to use your Sentry DSN with environment variables.

To enable readable stack traces, configure source maps upload for your production builds.

If you use a custom Express server in your Remix application, you should wrap your createRequestHandler function manually with wrapExpressCreateRequestHandler. This isn't necessary if you're using the built-in Remix App Server.

wrapExpressCreateRequestHandler is available starting with version 7.11.0.

server/index.ts
Copied
import { wrapExpressCreateRequestHandler } from "@sentry/remix";
import { createRequestHandler } from "@remix-run/express";

// ...

const createSentryRequestHandler = wrapExpressCreateRequestHandler(
  createRequestHandler,
);

app.all("*", createSentryRequestHandler(/* ... */));

For SDK versions < 7.106.0, the function returned by wrapExpressCreateRequestHandler accepts the build object as its first parameter. So if your boilerplate code passes the argument as a function, you need to update the usage. For example, if you're using Vite, you'll need to wait for the build loader before passing it to the wrapped handler.

server/index.ts
Copied
    wrapExpressCreateRequestHandler(createRequestHandler)({
      build: viteDevServer
-       ? () =>
-         viteDevServer.ssrLoadModule(
-           "virtual:remix/server-build"
-         )
+       ? await viteDevServer.ssrLoadModule(
+           "virtual:remix/server-build"
+         )
      : await import(BUILD_DIR + "/index.js"),
      ...
    })
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").