Sentry Integration
Error monitoring and performance tracking with Sentry
MemberPulse uses Sentry for error monitoring, performance tracking, and session replay. This guide covers the integration setup for the TanStack Start + Convex + React Query stack.
Architecture Overview
The Sentry integration consists of three parts:
- Client-side SDK - Captures browser errors, performance traces, and session replays
- Server-side instrumentation - Captures server errors and traces
- React Query integration - Reports Convex query/mutation errors to Sentry
Sentry only auto-captures uncaught exceptions. Errors handled by React Query (including Convex errors) require explicit reporting via the QueryCache and MutationCache handlers.
Installation
bun add @sentry/tanstackstart-react @sentry/vite-plugin
Configuration
Client-Side Setup
Initialize Sentry in src/router.tsx:
import * as Sentry from "@sentry/tanstackstart-react";
import { createRouter } from "@tanstack/react-router";
import { QueryClient, QueryCache, MutationCache } from "@tanstack/react-query";
import { ConvexQueryClient } from "@convex-dev/react-query";
import { ConvexReactClient } from "convex/react";
import { routeTree } from "./routeTree.gen";
const convex = new ConvexReactClient(import.meta.env.VITE_CONVEX_URL as string);
const convexQueryClient = new ConvexQueryClient(convex);
const queryClient = new QueryClient({
// Report React Query errors to Sentry
queryCache: new QueryCache({
onError: (error, query) => {
Sentry.captureException(error, {
tags: { type: "query" },
extra: { queryKey: query.queryKey },
});
},
}),
mutationCache: new MutationCache({
onError: (error, _variables, _context, mutation) => {
Sentry.captureException(error, {
tags: { type: "mutation" },
extra: { mutationKey: mutation.options.mutationKey },
});
},
}),
defaultOptions: {
queries: {
queryKeyHashFn: convexQueryClient.hashFn(),
queryFn: convexQueryClient.queryFn(),
},
},
});
convexQueryClient.connect(queryClient);
export const getRouter = () => {
const router = createRouter({
routeTree,
context: { queryClient, convex },
scrollRestoration: true,
defaultPreloadStaleTime: 0,
});
if (!router.isServer) {
Sentry.init({
dsn: "YOUR_SENTRY_DSN",
sendDefaultPii: true,
integrations: [
Sentry.tanstackRouterBrowserTracingIntegration(router),
Sentry.replayIntegration(),
],
tracesSampleRate: import.meta.env.DEV ? 1.0 : 0.2,
replaysSessionSampleRate: 0.1,
replaysOnErrorSampleRate: 1.0,
});
}
return router;
};
Server-Side Setup
Create instrument.server.mjs in the project root:
import * as Sentry from "@sentry/tanstackstart-react";
Sentry.init({
dsn: "YOUR_SENTRY_DSN",
sendDefaultPii: true,
tracesSampleRate: 1.0,
});
Create src/server.ts to wrap the server entry:
import { wrapFetchWithSentry } from "@sentry/tanstackstart-react";
import handler, { createServerEntry } from "@tanstack/react-start/server-entry";
export default createServerEntry(
wrapFetchWithSentry({
fetch(request: Request) {
return handler.fetch(request);
},
}),
);
Vite Plugin
Add the Sentry plugin to vite.config.ts:
import { sentryTanstackStart } from "@sentry/tanstackstart-react";
export default defineConfig({
plugins: [
// ... other plugins
sentryTanstackStart({
org: "your-org",
project: "your-project",
authToken: process.env.SENTRY_AUTH_TOKEN,
}),
],
});
Package Scripts
Update package.json to load the server instrumentation:
{
"scripts": {
"dev": "NODE_OPTIONS='--import ./instrument.server.mjs' vite dev --port 3000",
"build": "vite build && cp instrument.server.mjs .output/server",
"start": "node --import ./.output/server/instrument.server.mjs .output/server/index.mjs"
}
}
Why React Query Errors Need Manual Reporting
Sentry auto-captures uncaught exceptions and unhandled promise rejections. However, React Query catches all errors internally and renders them as error states rather than throwing.
This means:
| Error Type | Auto-captured by Sentry? |
|---|---|
Uncaught throw new Error() | Yes |
| Unhandled promise rejections | Yes |
Convex query errors via useQuery | No |
| Convex mutation errors | No |
The QueryCache and MutationCache onError handlers bridge this gap by explicitly reporting caught errors to Sentry with relevant context.
What Gets Captured
With this setup, Sentry captures:
- Uncaught exceptions - JavaScript errors that bubble up
- Convex query errors - Auth failures, validation errors, server errors
- Convex mutation errors - Write failures, conflicts
- Performance traces - Page loads, route transitions
- Session replays - Video-like recordings of user sessions with errors
Each error includes:
tags.type: "query" or "mutation"extra.queryKeyorextra.mutationKey: The operation that failed- Stack trace and source maps (in production builds)
Environment Variables
| Variable | Description |
|---|---|
SENTRY_AUTH_TOKEN | Auth token for source map uploads (build time only) |
VITE_SENTRY_DSN | Optional: DSN as env var instead of hardcoded |
The SENTRY_AUTH_TOKEN should only be available at build time, not shipped to the client. The DSN is safe to include in client code.
Ad Blocker Considerations
Some ad blockers block requests to sentry.io. Options:
- Accept it - Most users (~85%) don't have aggressive ad blockers
- Tunnel - Proxy Sentry requests through your own domain (adds complexity)
- Self-hosted - Run your own Sentry instance (enterprise use case)
For most applications, direct mode without tunneling is sufficient.