Skip to content

Commit 84db754

Browse files
authored
fix(v9/astro): Construct parametrized route during runtime (#17227)
Backport of #17190
1 parent 2398fc0 commit 84db754

File tree

2 files changed

+33
-11
lines changed

2 files changed

+33
-11
lines changed

dev-packages/e2e-tests/test-applications/astro-5/tests/tracing.dynamic.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,7 @@ test.describe('nested SSR routes (client, server, server request)', () => {
248248

249249
// Server HTTP request transaction
250250
expect(serverHTTPServerRequestTxn).toMatchObject({
251-
transaction: 'GET /api/user/myUsername123.json', // fixme: should be GET /api/user/[userId].json
251+
transaction: 'GET /api/user/[userId].json',
252252
transaction_info: { source: 'route' },
253253
contexts: {
254254
trace: {
@@ -278,13 +278,13 @@ test.describe('nested SSR routes (client, server, server request)', () => {
278278
await page.goto('/catchAll/hell0/whatever-do');
279279

280280
const routeNameMetaContent = await page.locator('meta[name="sentry-route-name"]').getAttribute('content');
281-
expect(routeNameMetaContent).toBe('%2FcatchAll%2F%5Bpath%5D'); // fixme: should be %2FcatchAll%2F%5B...path%5D
281+
expect(routeNameMetaContent).toBe('%2FcatchAll%2F%5B...path%5D');
282282

283283
const clientPageloadTxn = await clientPageloadTxnPromise;
284284
const serverPageRequestTxn = await serverPageRequestTxnPromise;
285285

286286
expect(clientPageloadTxn).toMatchObject({
287-
transaction: '/catchAll/[path]', // fixme: should be /catchAll/[...path]
287+
transaction: '/catchAll/[...path]',
288288
transaction_info: { source: 'route' },
289289
contexts: {
290290
trace: {
@@ -300,7 +300,7 @@ test.describe('nested SSR routes (client, server, server request)', () => {
300300
});
301301

302302
expect(serverPageRequestTxn).toMatchObject({
303-
transaction: 'GET /catchAll/[path]', // fixme: should be GET /catchAll/[...path]
303+
transaction: 'GET /catchAll/[...path]',
304304
transaction_info: { source: 'route' },
305305
contexts: {
306306
trace: {

packages/astro/src/server/middleware.ts

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,7 @@ import {
2222
startSpan,
2323
withIsolationScope,
2424
} from '@sentry/node';
25-
import type { APIContext, MiddlewareResponseHandler } from 'astro';
26-
import type { ResolvedRouteWithCasedPattern } from '../integration/types';
25+
import type { APIContext, MiddlewareResponseHandler, RoutePart } from 'astro';
2726

2827
type MiddlewareOptions = {
2928
/**
@@ -96,9 +95,6 @@ async function instrumentRequest(
9695
addNonEnumerableProperty(locals, '__sentry_wrapped__', true);
9796
}
9897

99-
const storedBuildTimeRoutes = (globalThis as unknown as { __sentryRouteInfo?: ResolvedRouteWithCasedPattern[] })
100-
?.__sentryRouteInfo;
101-
10298
const isDynamicPageRequest = checkIsDynamicPageRequest(ctx);
10399

104100
const request = ctx.request;
@@ -135,10 +131,21 @@ async function instrumentRequest(
135131
// `routePattern` is available after Astro 5
136132
const contextWithRoutePattern = ctx as Parameters<MiddlewareResponseHandler>[0] & { routePattern?: string };
137133
const rawRoutePattern = contextWithRoutePattern.routePattern;
138-
const foundRoute = storedBuildTimeRoutes?.find(route => route.pattern === rawRoutePattern);
134+
135+
// @ts-expect-error Implicit any on Symbol.for (This is available in Astro 5)
136+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
137+
const routesFromManifest = ctx?.[Symbol.for('context.routes')]?.manifest?.routes;
138+
139+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
140+
const matchedRouteSegmentsFromManifest = routesFromManifest?.find(
141+
(route: { routeData?: { route?: string } }) => route?.routeData?.route === rawRoutePattern,
142+
)?.routeData?.segments;
139143

140144
const parametrizedRoute =
141-
foundRoute?.patternCaseSensitive || interpolateRouteFromUrlAndParams(ctx.url.pathname, ctx.params);
145+
// Astro v5 - Joining the segments to get the correct casing of the parametrized route
146+
(matchedRouteSegmentsFromManifest && joinRouteSegments(matchedRouteSegmentsFromManifest)) ||
147+
// Fallback (Astro v4 and earlier)
148+
interpolateRouteFromUrlAndParams(ctx.url.pathname, ctx.params);
142149

143150
const source = parametrizedRoute ? 'route' : 'url';
144151
// storing res in a variable instead of directly returning is necessary to
@@ -365,3 +372,18 @@ function checkIsDynamicPageRequest(context: Parameters<MiddlewareResponseHandler
365372
return false;
366373
}
367374
}
375+
376+
/**
377+
* Join Astro route segments into a case-sensitive single path string.
378+
*
379+
* Astro lowercases the parametrized route. Joining segments manually is recommended to get the correct casing of the routes.
380+
* Recommendation in comment: https://github.com/withastro/astro/issues/13885#issuecomment-2934203029
381+
* Function Reference: https://github.com/joanrieu/astro-typed-links/blob/b3dc12c6fe8d672a2bc2ae2ccc57c8071bbd09fa/package/src/integration.ts#L16
382+
*/
383+
function joinRouteSegments(segments: RoutePart[][]): string {
384+
const parthArray = segments.map(segment =>
385+
segment.map(routePart => (routePart.dynamic ? `[${routePart.content}]` : routePart.content)).join(''),
386+
);
387+
388+
return `/${parthArray.join('/')}`;
389+
}

0 commit comments

Comments
 (0)