Skip to content

Commit faada7c

Browse files
authored
fix(astro): Construct parametrized route during runtime (#17190)
For v9, the changes were already reverted in this PR (#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 #17179
1 parent 33b23e0 commit faada7c

File tree

2 files changed

+32
-61
lines changed

2 files changed

+32
-61
lines changed

packages/astro/src/integration/index.ts

Lines changed: 3 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,14 @@
1-
import { readFileSync, writeFileSync } from 'node:fs';
2-
import { consoleSandbox, debug } from '@sentry/core';
1+
import { consoleSandbox } from '@sentry/core';
32
import { sentryVitePlugin } from '@sentry/vite-plugin';
4-
import type { AstroConfig, AstroIntegration, RoutePart } from 'astro';
3+
import type { AstroConfig, AstroIntegration } from 'astro';
54
import * as fs from 'fs';
65
import * as path from 'path';
76
import { buildClientSnippet, buildSdkInitFileImportSnippet, buildServerSnippet } from './snippets';
8-
import type { IntegrationResolvedRoute, SentryOptions } from './types';
7+
import type { SentryOptions } from './types';
98

109
const PKG_NAME = '@sentry/astro';
1110

1211
export const sentryAstro = (options: SentryOptions = {}): AstroIntegration => {
13-
let sentryServerInitPath: string | undefined;
14-
let didSaveRouteData = false;
15-
1612
return {
1713
name: PKG_NAME,
1814
hooks: {
@@ -138,8 +134,6 @@ export const sentryAstro = (options: SentryOptions = {}): AstroIntegration => {
138134
injectScript('page-ssr', buildServerSnippet(options || {}));
139135
}
140136

141-
sentryServerInitPath = pathToServerInit;
142-
143137
// Prevent Sentry from being externalized for SSR.
144138
// Cloudflare like environments have Node.js APIs are available under `node:` prefix.
145139
// Ref: https://developers.cloudflare.com/workers/runtime-apis/nodejs/
@@ -171,36 +165,6 @@ export const sentryAstro = (options: SentryOptions = {}): AstroIntegration => {
171165
});
172166
}
173167
},
174-
175-
// @ts-expect-error - This hook is available in Astro 5+
176-
'astro:routes:resolved': ({ routes }: { routes: IntegrationResolvedRoute[] }) => {
177-
if (!sentryServerInitPath || didSaveRouteData) {
178-
return;
179-
}
180-
181-
try {
182-
const serverInitContent = readFileSync(sentryServerInitPath, 'utf8');
183-
184-
const updatedServerInitContent = `${serverInitContent}\nglobalThis["__sentryRouteInfo"] = ${JSON.stringify(
185-
routes.map(route => {
186-
return {
187-
...route,
188-
patternCaseSensitive: joinRouteSegments(route.segments), // Store parametrized routes with correct casing on `globalThis` to be able to use them on the server during runtime
189-
patternRegex: route.patternRegex.source, // using `source` to be able to serialize the regex
190-
};
191-
}),
192-
null,
193-
2,
194-
)};`;
195-
196-
writeFileSync(sentryServerInitPath, updatedServerInitContent, 'utf8');
197-
198-
didSaveRouteData = true; // Prevents writing the file multiple times during runtime
199-
debug.log('Successfully added route pattern information to Sentry config file:', sentryServerInitPath);
200-
} catch (error) {
201-
debug.warn(`Failed to write to Sentry config file at ${sentryServerInitPath}:`, error);
202-
}
203-
},
204168
},
205169
};
206170
};
@@ -307,18 +271,3 @@ export function getUpdatedSourceMapSettings(
307271

308272
return { previousUserSourceMapSetting, updatedSourceMapSetting };
309273
}
310-
311-
/**
312-
* Join Astro route segments into a case-sensitive single path string.
313-
*
314-
* Astro lowercases the parametrized route. Joining segments manually is recommended to get the correct casing of the routes.
315-
* Recommendation in comment: https://github.com/withastro/astro/issues/13885#issuecomment-2934203029
316-
* Function Reference: https://github.com/joanrieu/astro-typed-links/blob/b3dc12c6fe8d672a2bc2ae2ccc57c8071bbd09fa/package/src/integration.ts#L16
317-
*/
318-
function joinRouteSegments(segments: RoutePart[][]): string {
319-
const parthArray = segments.map(segment =>
320-
segment.map(routePart => (routePart.dynamic ? `[${routePart.content}]` : routePart.content)).join(''),
321-
);
322-
323-
return `/${parthArray.join('/')}`;
324-
}

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)