Skip to content

Commit 76c48a1

Browse files
committed
fix(astro): Construct parametrized route during runtime
1 parent ee63f5f commit 76c48a1

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)