From 7d8fb4c3366a4fadfddf32a5fccec8b00fbb8a3e Mon Sep 17 00:00:00 2001 From: Abdelrahman Awad Date: Mon, 31 Aug 2026 15:46:34 -0400 Subject: [PATCH 1/5] feat(nuxt): Register a route provider outside the tracing guard Nuxt installs the router before its plugins run, so the client plugin can read it straight off `nuxtApp` rather than waiting for a Vue app the way `@sentry/vue` does. Registered outside the `__SENTRY_TRACING__` guard, so route parameterization survives when tracing is tree-shaken away. Reuses `createVueRouteProvider` rather than reimplementing the vue-router resolve handling. --- .../nuxt/src/runtime/plugins/sentry.client.ts | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/packages/nuxt/src/runtime/plugins/sentry.client.ts b/packages/nuxt/src/runtime/plugins/sentry.client.ts index 6d03b3cd9625..c27f6de11e05 100644 --- a/packages/nuxt/src/runtime/plugins/sentry.client.ts +++ b/packages/nuxt/src/runtime/plugins/sentry.client.ts @@ -1,5 +1,5 @@ -import { getClient, GLOBAL_OBJ } from '@sentry/core'; -import { browserTracingIntegration, vueIntegration } from '@sentry/vue'; +import { getClient, GLOBAL_OBJ, setRouteProvider } from '@sentry/core'; +import { browserTracingIntegration, createVueRouteProvider, vueIntegration } from '@sentry/vue'; import { defineNuxtPlugin, isNuxtError } from 'nuxt/app'; import type { GlobalObjWithIntegrationOptions } from '../../client/vueIntegration'; import { reportNuxtError } from '../utils'; @@ -35,6 +35,18 @@ export default defineNuxtPlugin({ name: 'sentry-client-integrations', dependsOn: ['sentry-client-config'], async setup(nuxtApp) { + // Registered outside the tracing guard, because route parameterization should not depend on + // tracing: anything that needs a route name (bfcache metrics, web vitals) can resolve one even + // when tracing is tree-shaken away. Nuxt installs the router before its plugins run, so unlike + // `@sentry/vue` this can read it straight off `nuxtApp`. + const client = getClient(); + if (client && '$router' in nuxtApp) { + setRouteProvider( + createVueRouteProvider(() => nuxtApp.$router), + client, + ); + } + // This evaluates to true unless __SENTRY_TRACING__ is text-replaced with "false", in which case everything inside // will get tree-shaken away if (typeof __SENTRY_TRACING__ === 'undefined' || __SENTRY_TRACING__) { From 4a34d18b2fcec2780617e08f3f1c7441c96054af Mon Sep 17 00:00:00 2001 From: Abdelrahman Awad Date: Thu, 24 Sep 2026 14:03:59 -0400 Subject: [PATCH 2/5] ref(nuxt): Import `setRouteProvider` from `@sentry/vue` --- packages/nuxt/src/runtime/plugins/sentry.client.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/nuxt/src/runtime/plugins/sentry.client.ts b/packages/nuxt/src/runtime/plugins/sentry.client.ts index c27f6de11e05..0ba1dfe021db 100644 --- a/packages/nuxt/src/runtime/plugins/sentry.client.ts +++ b/packages/nuxt/src/runtime/plugins/sentry.client.ts @@ -1,5 +1,5 @@ -import { getClient, GLOBAL_OBJ, setRouteProvider } from '@sentry/core'; -import { browserTracingIntegration, createVueRouteProvider, vueIntegration } from '@sentry/vue'; +import { getClient, GLOBAL_OBJ } from '@sentry/core'; +import { browserTracingIntegration, createVueRouteProvider, setRouteProvider, vueIntegration } from '@sentry/vue'; import { defineNuxtPlugin, isNuxtError } from 'nuxt/app'; import type { GlobalObjWithIntegrationOptions } from '../../client/vueIntegration'; import { reportNuxtError } from '../utils'; From 72d850767a772f73b02a38f54b06a2bde8f1da26 Mon Sep 17 00:00:00 2001 From: Abdelrahman Awad Date: Thu, 24 Sep 2026 14:14:28 -0400 Subject: [PATCH 3/5] fix(nuxt): Type the router handed to the Vue route provider --- packages/nuxt/src/runtime/plugins/sentry.client.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/nuxt/src/runtime/plugins/sentry.client.ts b/packages/nuxt/src/runtime/plugins/sentry.client.ts index 0ba1dfe021db..7169053e0de4 100644 --- a/packages/nuxt/src/runtime/plugins/sentry.client.ts +++ b/packages/nuxt/src/runtime/plugins/sentry.client.ts @@ -28,6 +28,8 @@ interface VueRouter { beforeEach: (fn: (to: Route, from: Route, next?: () => void) => void) => void; } +type VueRouteProviderRouter = ReturnType[0]>; + // Tree-shakable guard to remove all code related to tracing declare const __SENTRY_TRACING__: boolean; @@ -42,7 +44,7 @@ export default defineNuxtPlugin({ const client = getClient(); if (client && '$router' in nuxtApp) { setRouteProvider( - createVueRouteProvider(() => nuxtApp.$router), + createVueRouteProvider(() => nuxtApp.$router as VueRouteProviderRouter), client, ); } From 4ef84b59c156e131d7273e2a71305d08090619c6 Mon Sep 17 00:00:00 2001 From: Abdelrahman Awad Date: Thu, 24 Sep 2026 15:59:31 -0400 Subject: [PATCH 4/5] fix(nuxt): Keep a `routeProvider` passed to `Sentry.init` --- packages/nuxt/src/runtime/plugins/sentry.client.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/nuxt/src/runtime/plugins/sentry.client.ts b/packages/nuxt/src/runtime/plugins/sentry.client.ts index 7169053e0de4..a9969c3be2f7 100644 --- a/packages/nuxt/src/runtime/plugins/sentry.client.ts +++ b/packages/nuxt/src/runtime/plugins/sentry.client.ts @@ -1,5 +1,11 @@ import { getClient, GLOBAL_OBJ } from '@sentry/core'; -import { browserTracingIntegration, createVueRouteProvider, setRouteProvider, vueIntegration } from '@sentry/vue'; +import { + browserTracingIntegration, + createVueRouteProvider, + getRouteProvider, + setRouteProvider, + vueIntegration, +} from '@sentry/vue'; import { defineNuxtPlugin, isNuxtError } from 'nuxt/app'; import type { GlobalObjWithIntegrationOptions } from '../../client/vueIntegration'; import { reportNuxtError } from '../utils'; @@ -42,7 +48,8 @@ export default defineNuxtPlugin({ // when tracing is tree-shaken away. Nuxt installs the router before its plugins run, so unlike // `@sentry/vue` this can read it straight off `nuxtApp`. const client = getClient(); - if (client && '$router' in nuxtApp) { + // A `routeProvider` passed to `Sentry.init` is the user's choice, so it is left in place. + if (client && '$router' in nuxtApp && !getRouteProvider(client)) { setRouteProvider( createVueRouteProvider(() => nuxtApp.$router as VueRouteProviderRouter), client, From 25a1d20eca86000f8083322ec65cf001f9a1a9c3 Mon Sep 17 00:00:00 2001 From: Abdelrahman Awad Date: Thu, 24 Sep 2026 17:20:19 -0400 Subject: [PATCH 5/5] test(nuxt): Cover route resolution through the route provider --- .../nuxt-5/app/pages/route-provider/[id].vue | 14 ++++++++++++++ .../nuxt-5/tests/route-provider.test.ts | 8 ++++++++ 2 files changed, 22 insertions(+) create mode 100644 dev-packages/e2e-tests/test-applications/nuxt-5/app/pages/route-provider/[id].vue create mode 100644 dev-packages/e2e-tests/test-applications/nuxt-5/tests/route-provider.test.ts diff --git a/dev-packages/e2e-tests/test-applications/nuxt-5/app/pages/route-provider/[id].vue b/dev-packages/e2e-tests/test-applications/nuxt-5/app/pages/route-provider/[id].vue new file mode 100644 index 000000000000..15ecf3348e94 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nuxt-5/app/pages/route-provider/[id].vue @@ -0,0 +1,14 @@ + + + diff --git a/dev-packages/e2e-tests/test-applications/nuxt-5/tests/route-provider.test.ts b/dev-packages/e2e-tests/test-applications/nuxt-5/tests/route-provider.test.ts new file mode 100644 index 000000000000..4796384fbb47 --- /dev/null +++ b/dev-packages/e2e-tests/test-applications/nuxt-5/tests/route-provider.test.ts @@ -0,0 +1,8 @@ +import { expect, test } from '@playwright/test'; + +// The route provider reads the router off the Nuxt app, so this fails if the plugin never registers it. +test('resolves the parameterized route through the route provider', async ({ page }) => { + await page.goto('/route-provider/123'); + + await expect(page.locator('#resolved-route')).toHaveText('/route-provider/:id()'); +});