From 0ced0b19b662dd9a3ee05cc50472785c128b2eb6 Mon Sep 17 00:00:00 2001 From: Sigrid Huemer <32902192+s1gr1d@users.noreply.github.com> Date: Tue, 29 Jul 2025 16:28:24 +0200 Subject: [PATCH 1/2] fix(astro): Construct parametrized route during runtime (#17190) For v9, the changes were already reverted in this PR (https://github.com/getsentry/sentry-javascript/issues/17179) to create a quick fix we can release soon. However, this removed some parametrization. This PR here not only fixed the problem with continuously writing to `globalThis` to share build-time data with the runtime (we don't do this anymore). The route parametrization now happens only during runtime, as we have access to the route segments at runtime with Astro v5. This adds a **little** performance overhead when compared with the previous approach (the route segments are now constructed during runtime) - but this is not an expensive operation. The `.find` method was used in the previous approach as well. Fixes https://github.com/getsentry/sentry-javascript/issues/17179 --- packages/astro/src/server/middleware.ts | 36 ++++++++++++++++++++----- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/packages/astro/src/server/middleware.ts b/packages/astro/src/server/middleware.ts index 9f04d5427fcf..19aa11af0767 100644 --- a/packages/astro/src/server/middleware.ts +++ b/packages/astro/src/server/middleware.ts @@ -22,8 +22,7 @@ import { startSpan, withIsolationScope, } from '@sentry/node'; -import type { APIContext, MiddlewareResponseHandler } from 'astro'; -import type { ResolvedRouteWithCasedPattern } from '../integration/types'; +import type { APIContext, MiddlewareResponseHandler, RoutePart } from 'astro'; type MiddlewareOptions = { /** @@ -96,9 +95,6 @@ async function instrumentRequest( addNonEnumerableProperty(locals, '__sentry_wrapped__', true); } - const storedBuildTimeRoutes = (globalThis as unknown as { __sentryRouteInfo?: ResolvedRouteWithCasedPattern[] }) - ?.__sentryRouteInfo; - const isDynamicPageRequest = checkIsDynamicPageRequest(ctx); const request = ctx.request; @@ -135,10 +131,21 @@ async function instrumentRequest( // `routePattern` is available after Astro 5 const contextWithRoutePattern = ctx as Parameters[0] & { routePattern?: string }; const rawRoutePattern = contextWithRoutePattern.routePattern; - const foundRoute = storedBuildTimeRoutes?.find(route => route.pattern === rawRoutePattern); + + // @ts-expect-error Implicit any on Symbol.for (This is available in Astro 5) + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + const routesFromManifest = ctx?.[Symbol.for('context.routes')]?.manifest?.routes; + + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + const matchedRouteSegmentsFromManifest = routesFromManifest?.find( + (route: { routeData?: { route?: string } }) => route?.routeData?.route === rawRoutePattern, + )?.routeData?.segments; const parametrizedRoute = - foundRoute?.patternCaseSensitive || interpolateRouteFromUrlAndParams(ctx.url.pathname, ctx.params); + // Astro v5 - Joining the segments to get the correct casing of the parametrized route + (matchedRouteSegmentsFromManifest && joinRouteSegments(matchedRouteSegmentsFromManifest)) || + // Fallback (Astro v4 and earlier) + interpolateRouteFromUrlAndParams(ctx.url.pathname, ctx.params); const source = parametrizedRoute ? 'route' : 'url'; // storing res in a variable instead of directly returning is necessary to @@ -365,3 +372,18 @@ function checkIsDynamicPageRequest(context: Parameters + segment.map(routePart => (routePart.dynamic ? `[${routePart.content}]` : routePart.content)).join(''), + ); + + return `/${parthArray.join('/')}`; +} From 94b8017acba6a747b8058584ea8cb2fb4e523253 Mon Sep 17 00:00:00 2001 From: s1gr1d Date: Wed, 30 Jul 2025 13:56:53 +0200 Subject: [PATCH 2/2] fix(v9/astro): Construct parametrized route during runtime --- .../astro-5/tests/tracing.dynamic.test.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/astro-5/tests/tracing.dynamic.test.ts b/dev-packages/e2e-tests/test-applications/astro-5/tests/tracing.dynamic.test.ts index 66f9e9595920..b7dda807c65c 100644 --- a/dev-packages/e2e-tests/test-applications/astro-5/tests/tracing.dynamic.test.ts +++ b/dev-packages/e2e-tests/test-applications/astro-5/tests/tracing.dynamic.test.ts @@ -248,7 +248,7 @@ test.describe('nested SSR routes (client, server, server request)', () => { // Server HTTP request transaction expect(serverHTTPServerRequestTxn).toMatchObject({ - transaction: 'GET /api/user/myUsername123.json', // fixme: should be GET /api/user/[userId].json + transaction: 'GET /api/user/[userId].json', transaction_info: { source: 'route' }, contexts: { trace: { @@ -278,13 +278,13 @@ test.describe('nested SSR routes (client, server, server request)', () => { await page.goto('/catchAll/hell0/whatever-do'); const routeNameMetaContent = await page.locator('meta[name="sentry-route-name"]').getAttribute('content'); - expect(routeNameMetaContent).toBe('%2FcatchAll%2F%5Bpath%5D'); // fixme: should be %2FcatchAll%2F%5B...path%5D + expect(routeNameMetaContent).toBe('%2FcatchAll%2F%5B...path%5D'); const clientPageloadTxn = await clientPageloadTxnPromise; const serverPageRequestTxn = await serverPageRequestTxnPromise; expect(clientPageloadTxn).toMatchObject({ - transaction: '/catchAll/[path]', // fixme: should be /catchAll/[...path] + transaction: '/catchAll/[...path]', transaction_info: { source: 'route' }, contexts: { trace: { @@ -300,7 +300,7 @@ test.describe('nested SSR routes (client, server, server request)', () => { }); expect(serverPageRequestTxn).toMatchObject({ - transaction: 'GET /catchAll/[path]', // fixme: should be GET /catchAll/[...path] + transaction: 'GET /catchAll/[...path]', transaction_info: { source: 'route' }, contexts: { trace: {