Closed
Description
Is there an existing issue for this?
- I have checked for existing issues https://github.com/getsentry/sentry-javascript/issues
- I have reviewed the documentation https://docs.sentry.io/
- I am using the latest SDK release https://github.com/getsentry/sentry-javascript/releases
How do you use Sentry?
Sentry Saas (sentry.io)
Which SDK are you using?
@sentry/react-router
SDK Version
9.12.0
Framework Version
react-router v7 previously remix, using vite, based on epic-stack
Link to Sentry event
No response
Reproduction Example/SDK Setup
The best place to look for reproduction is the epic-stack. I've followed their integration in my app. Releases, backend performance monitoring, and error capturing all seem to work, but frontend performance monitoring is not working in Sentry despite seeing events sent by the frontend.
Issue first reported here: #14519 (comment)
Steps to Reproduce
Followed epic-stack example for sentry / react-router v7 / vite integration
// monitoring.client.tsx
import * as Sentry from '@sentry/react-router'
export function init() {
Sentry.init({
dsn: ENV.SENTRY_DSN,
environment: ENV.SENTRY_ENV,
beforeSend(event) {
if (event.request?.url) {
const url = new URL(event.request.url)
if (
url.protocol === 'chrome-extension:' ||
url.protocol === 'moz-extension:'
) {
// This error is from a browser extension, ignore it
return null
}
}
return event
},
integrations: [
Sentry.replayIntegration(),
Sentry.browserProfilingIntegration(),
],
// Set tracesSampleRate to 1.0 to capture 100%
// of transactions for performance monitoring.
// We recommend adjusting this value in production
tracesSampleRate: 1.0,
// Capture Replay for 10% of all sessions,
// plus for 100% of sessions with an error
replaysSessionSampleRate: 0.1,
replaysOnErrorSampleRate: 1.0,
})
}
//react-router.config.ts
import { type Config } from '@react-router/dev/config'
import { sentryOnBuildEnd } from '@sentry/react-router'
const MODE = process.env.NODE_ENV
export default {
// Defaults to true. Set to false to enable SPA for all routes.
ssr: true,
future: {
unstable_optimizeDeps: true,
},
buildEnd: async ({ viteConfig, reactRouterConfig, buildManifest }) => {
if (MODE === 'production' && process.env.SENTRY_AUTH_TOKEN) {
await sentryOnBuildEnd({
viteConfig,
reactRouterConfig,
buildManifest,
})
}
},
} satisfies Config
// monitoring.ts
import prismaInstrumentation from '@prisma/instrumentation'
import { nodeProfilingIntegration } from '@sentry/profiling-node'
import * as Sentry from '@sentry/react-router'
// prisma's exports are wrong...
// https://github.com/prisma/prisma/issues/23410
const { PrismaInstrumentation } = prismaInstrumentation
export function init() {
Sentry.init({
dsn: process.env.SENTRY_DSN,
environment: process.env.SENTRY_ENV,
denyUrls: [
/\/resources\/healthcheck/,
// TODO: be smarter about the public assets...
/\/build\//,
/\/favicons\//,
/\/img\//,
/\/fonts\//,
/\/favicon.ico/,
/\/site\.webmanifest/,
],
integrations: [
Sentry.prismaIntegration({
prismaInstrumentation: new PrismaInstrumentation(),
}),
Sentry.httpIntegration(),
nodeProfilingIntegration(),
],
tracesSampler(samplingContext) {
// ignore healthcheck transactions by other services (consul, etc.)
if (samplingContext.request?.url?.includes('/resources/healthcheck')) {
return 0
}
return 1
},
beforeSendTransaction(event) {
// ignore all healthcheck related transactions
// note that name of header here is case-sensitive
if (event.request?.headers?.['x-healthcheck'] === 'true') {
return null
}
return event
},
})
}
// index.ts
import * as Sentry from '@sentry/react-router'
// ...
const SENTRY_ENABLED = IS_PROD && process.env.SENTRY_DSN
if (SENTRY_ENABLED) {
import('./utils/monitoring.js').then(({ init }) => init()).catch(console.log)
}
// this is actually different than the most recent epic-stack version which doesn't use helmet
app.use(
helmet({
xPoweredBy: false,
referrerPolicy: { policy: 'same-origin' },
crossOriginEmbedderPolicy: false,
contentSecurityPolicy: {
directives: {
'connect-src': [
MODE === 'development' ? 'ws:' : null,
process.env.SENTRY_DSN ? '*.sentry.io' : null,
"'self'",
].filter(Boolean),
// ...
},
},
}),
)
// ...
closeWithGrace(async ({ err }) => {
await new Promise((resolve, reject) => {
server.close((e) => (e ? reject(e) : resolve('ok')))
})
if (err) {
console.error(chalk.red(err))
console.error(chalk.red(err.stack))
if (SENTRY_ENABLED) {
Sentry.captureException(err)
await Sentry.flush(500)
}
}
})
// vite.config.ts
import { createRequire } from 'node:module'
import path from 'node:path'
import { reactRouter } from '@react-router/dev/vite'
import {
sentryReactRouter,
type SentryReactRouterBuildOptions,
} from '@sentry/react-router'
import { defineConfig } from 'vite'
import { envOnlyMacros } from 'vite-env-only'
import { viteStaticCopy } from 'vite-plugin-static-copy'
const require = createRequire(import.meta.url)
const pdfjsDistPath = path.dirname(require.resolve('pdfjs-dist/package.json'))
const MODE = process.env.NODE_ENV
const sentryConfig: SentryReactRouterBuildOptions = {
authToken: process.env.SENTRY_AUTH_TOKEN,
org: 'xxx',
project: 'xxx',
unstable_sentryVitePluginOptions: {
release: {
name: process.env.COMMIT_SHA,
setCommits: {
auto: true,
},
},
sourcemaps: {
filesToDeleteAfterUpload: ['./build/**/*.map', '.server-build/**/*.map'],
},
},
}
export default defineConfig((config) => ({
build: {
cssMinify: MODE === 'production',
rollupOptions: {
external: [/node:.*/, 'fsevents'],
},
assetsInlineLimit: (source: string) => {
if (
source.endsWith('sprite.svg') ||
source.endsWith('favicon.svg') ||
source.endsWith('apple-touch-icon.png')
) {
return false
}
},
sourcemap: true,
},
optimizeDeps: {
exclude: ['execa', 'npm-run-path', 'unicorn-magic'],
},
server: {
watch: {
ignored: ['**/playwright-report/**'],
},
},
sentryConfig,
plugins: [
envOnlyMacros(),
// it would be really nice to have this enabled in tests, but we'll have to
// wait until https://github.com/remix-run/remix/issues/9871 is fixed
MODE === 'test' ? null : reactRouter(),
MODE === 'production' && process.env.SENTRY_AUTH_TOKEN
? sentryReactRouter(sentryConfig, config)
: null,
viteStaticCopy({
targets: [
{
src: path.join(pdfjsDistPath, 'build'),
dest: 'pdfjs-dist',
},
],
}),
],
test: {
include: ['./app/**/*.test.{ts,tsx}'],
setupFiles: ['./tests/setup/setup-test-env.ts'],
globalSetup: ['./tests/setup/global-setup.ts'],
restoreMocks: true,
coverage: {
include: ['app/**/*.{ts,tsx}'],
exclude: ['app/components/**/*.{ts,tsx}', 'app/emails/**/*.{ts,tsx}'],
all: true,
},
},
ssr: {
noExternal: [/@atlaskit\/pragmatic-drag-and-drop.*/, '@silk-hq/components'],
},
}))
//entry.server.ts (abbreviated)
import * as Sentry from '@sentry/node'
export default async function handleRequest(...args: DocRequestArgs) {
// ...
if (process.env.NODE_ENV === 'production' && process.env.SENTRY_DSN) {
responseHeaders.append('Document-Policy', 'js-profiling')
}
// ...
}
// ...
export function handleError(
error: unknown,
{ request }: LoaderFunctionArgs | ActionFunctionArgs,
): void {
// Skip capturing if the request is aborted as Remix docs suggest
// Ref: https://remix.run/docs/en/main/file-conventions/entry.server#handleerror
if (request.signal.aborted) {
return
}
if (error instanceof Error) {
console.error(chalk.red(error.stack))
void Sentry.captureException(error)
} else {
console.error(error)
Sentry.captureException(error)
}
}
Expected Result
I should see frontend performance monitoring info.
Actual Result
Events sent from frontend:
// event sent from frontend
fetch("https://xxx/envelope/?sentry_version=7&sentry_key=xxx&sentry_client=sentry.javascript.react-router%2F9.5.0", {
"headers": {
"accept": "*/*",
"accept-language": "en-US,en;q=0.9",
"content-type": "text/plain;charset=UTF-8",
"priority": "u=1, i",
"sec-ch-ua": "\"Chromium\";v=\"134\", \"Not:A-Brand\";v=\"24\", \"Google Chrome\";v=\"134\"",
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": "\"macOS\"",
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "cross-site",
"Referer": "xxx",
"Referrer-Policy": "strict-origin"
},
"body": "{\"sent_at\":\"2025-04-12T15:41:38.217Z\",\"sdk\":{\"name\":\"sentry.javascript.react-router\",\"version\":\"9.5.0\"}}\n{\"type\":\"session\"}\n{\"sid\":\"xxx\",\"init\":true,\"started\":\"2025-04-12T15:41:38.217Z\",\"timestamp\":\"2025-04-12T15:41:38.217Z\",\"status\":\"ok\",\"errors\":0,\"attrs\":{\"release\":\"xxx\",\"environment\":\"production\",\"user_agent\":\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36\"}}",
"method": "POST"
});
Metadata
Metadata
Assignees
Type
Projects
Status
Waiting for: Product Owner