+ {data.greeting}
+
+ data: {JSON.stringify(data)}
+
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 eb70f7362e63..36f32cd5dc0e 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
@@ -119,3 +119,147 @@ test.describe('tracing in dynamically rendered (ssr) routes', () => {
});
});
});
+
+test.describe('nested SSR routes (client, server, server request)', () => {
+ /** The user-page route fetches from an endpoint and creates a deeply nested span structure:
+ * pageload — /user-page/myUsername123
+ * ├── browser.** — multiple browser spans
+ * └── browser.request — /user-page/myUsername123
+ * └── http.server — GET /user-page/[userId] (SSR page request)
+ * └── http.client — GET /api/user/myUsername123.json (executing fetch call from SSR page - span)
+ * └── http.server — GET /api/user/myUsername123.json (server request)
+ */
+ test('sends connected server and client pageload and request spans with the same trace id', async ({ page }) => {
+ const clientPageloadTxnPromise = waitForTransaction('astro-5', txnEvent => {
+ return txnEvent?.transaction?.startsWith('/user-page/') ?? false;
+ });
+
+ const serverPageRequestTxnPromise = waitForTransaction('astro-5', txnEvent => {
+ return txnEvent?.transaction?.startsWith('GET /user-page/') ?? false;
+ });
+
+ const serverHTTPServerRequestTxnPromise = waitForTransaction('astro-5', txnEvent => {
+ return txnEvent?.transaction?.startsWith('GET /api/user/') ?? false;
+ });
+
+ await page.goto('/user-page/myUsername123');
+
+ const clientPageloadTxn = await clientPageloadTxnPromise;
+ const serverPageRequestTxn = await serverPageRequestTxnPromise;
+ const serverHTTPServerRequestTxn = await serverHTTPServerRequestTxnPromise;
+ const serverRequestHTTPClientSpan = serverPageRequestTxn.spans?.find(
+ span => span.op === 'http.client' && span.description?.includes('/api/user/'),
+ );
+
+ const clientPageloadTraceId = clientPageloadTxn.contexts?.trace?.trace_id;
+
+ // Verify all spans have the same trace ID
+ expect(clientPageloadTraceId).toEqual(serverPageRequestTxn.contexts?.trace?.trace_id);
+ expect(clientPageloadTraceId).toEqual(serverHTTPServerRequestTxn.contexts?.trace?.trace_id);
+ expect(clientPageloadTraceId).toEqual(serverRequestHTTPClientSpan?.trace_id);
+
+ // serverPageRequest has no parent (root span)
+ expect(serverPageRequestTxn.contexts?.trace?.parent_span_id).toBeUndefined();
+
+ // clientPageload's parent and serverRequestHTTPClient's parent is serverPageRequest
+ const serverPageRequestSpanId = serverPageRequestTxn.contexts?.trace?.span_id;
+ expect(clientPageloadTxn.contexts?.trace?.parent_span_id).toEqual(serverPageRequestSpanId);
+ expect(serverRequestHTTPClientSpan?.parent_span_id).toEqual(serverPageRequestSpanId);
+
+ // serverHTTPServerRequest's parent is serverRequestHTTPClient
+ expect(serverHTTPServerRequestTxn.contexts?.trace?.parent_span_id).toEqual(serverRequestHTTPClientSpan?.span_id);
+ });
+
+ test('sends parametrized pageload, server and API request transaction names', async ({ page }) => {
+ const clientPageloadTxnPromise = waitForTransaction('astro-5', txnEvent => {
+ return txnEvent?.transaction?.startsWith('/user-page/') ?? false;
+ });
+
+ const serverPageRequestTxnPromise = waitForTransaction('astro-5', txnEvent => {
+ return txnEvent?.transaction?.startsWith('GET /user-page/') ?? false;
+ });
+
+ const serverHTTPServerRequestTxnPromise = waitForTransaction('astro-5', txnEvent => {
+ return txnEvent?.transaction?.startsWith('GET /api/user/') ?? false;
+ });
+
+ await page.goto('/user-page/myUsername123');
+
+ const clientPageloadTxn = await clientPageloadTxnPromise;
+ const serverPageRequestTxn = await serverPageRequestTxnPromise;
+ const serverHTTPServerRequestTxn = await serverHTTPServerRequestTxnPromise;
+
+ const serverRequestHTTPClientSpan = serverPageRequestTxn.spans?.find(
+ span => span.op === 'http.client' && span.description?.includes('/api/user/'),
+ );
+
+ // Client pageload transaction - actual URL with pageload operation
+ expect(clientPageloadTxn).toMatchObject({
+ transaction: '/user-page/myUsername123', // todo: parametrize to '/user-page/[userId]'
+ transaction_info: { source: 'url' },
+ contexts: {
+ trace: {
+ op: 'pageload',
+ origin: 'auto.pageload.browser',
+ data: {
+ 'sentry.op': 'pageload',
+ 'sentry.origin': 'auto.pageload.browser',
+ 'sentry.source': 'url',
+ },
+ },
+ },
+ });
+
+ // Server page request transaction - parametrized transaction name with actual URL in data
+ expect(serverPageRequestTxn).toMatchObject({
+ transaction: 'GET /user-page/[userId]',
+ transaction_info: { source: 'route' },
+ contexts: {
+ trace: {
+ op: 'http.server',
+ origin: 'auto.http.astro',
+ data: {
+ 'sentry.op': 'http.server',
+ 'sentry.origin': 'auto.http.astro',
+ 'sentry.source': 'route',
+ url: expect.stringContaining('/user-page/myUsername123'),
+ },
+ },
+ },
+ request: { url: expect.stringContaining('/user-page/myUsername123') },
+ });
+
+ // HTTP client span - actual API URL with client operation
+ expect(serverRequestHTTPClientSpan).toMatchObject({
+ op: 'http.client',
+ origin: 'auto.http.otel.node_fetch',
+ description: 'GET http://localhost:3030/api/user/myUsername123.json', // todo: parametrize (this is just a span though - no transaction)
+ data: {
+ 'sentry.op': 'http.client',
+ 'sentry.origin': 'auto.http.otel.node_fetch',
+ 'url.full': expect.stringContaining('/api/user/myUsername123.json'),
+ 'url.path': '/api/user/myUsername123.json',
+ url: expect.stringContaining('/api/user/myUsername123.json'),
+ },
+ });
+
+ // Server HTTP request transaction - should be parametrized (todo: currently not parametrized)
+ expect(serverHTTPServerRequestTxn).toMatchObject({
+ transaction: 'GET /api/user/myUsername123.json', // todo: should be parametrized to 'GET /api/user/[userId].json'
+ transaction_info: { source: 'route' },
+ contexts: {
+ trace: {
+ op: 'http.server',
+ origin: 'auto.http.astro',
+ data: {
+ 'sentry.op': 'http.server',
+ 'sentry.origin': 'auto.http.astro',
+ 'sentry.source': 'route',
+ url: expect.stringContaining('/api/user/myUsername123.json'),
+ },
+ },
+ },
+ request: { url: expect.stringContaining('/api/user/myUsername123.json') },
+ });
+ });
+});