Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,7 @@ test('should create a parameterized transaction when the `app` directory is used
});
});

test('should create a static transaction when the `app` directory is used and the route is not parameterized', async ({
page,
}) => {
test('should create a transaction named after the static route when the `app` directory is used', async ({ page }) => {
const transactionPromise = waitForTransaction('nextjs-14', async transactionEvent => {
return (
transactionEvent.transaction === '/parameterized/static' && transactionEvent.contexts?.trace?.op === 'pageload'
Expand All @@ -63,9 +61,10 @@ test('should create a static transaction when the `app` directory is used and th
data: {
'sentry.op': 'pageload',
'sentry.origin': 'auto.pageload.nextjs.app_router_instrumentation',
'sentry.source': 'url',
'sentry.source': 'route',
'url.full': expect.stringMatching(/^https?:\/\/localhost:\d+\/parameterized\/static$/),
'url.path': '/parameterized/static',
'url.template': '/parameterized/static',
},
op: 'pageload',
origin: 'auto.pageload.nextjs.app_router_instrumentation',
Expand All @@ -81,11 +80,9 @@ test('should create a static transaction when the `app` directory is used and th
start_timestamp: expect.any(Number),
timestamp: expect.any(Number),
transaction: '/parameterized/static',
transaction_info: { source: 'url' },
transaction_info: { source: 'route' },
type: 'transaction',
});

expect(transaction.contexts?.trace?.data).not.toHaveProperty('url.template');
});

test('should create a partially parameterized transaction when the `app` directory is used', async ({ page }) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,7 @@ test('should create a parameterized transaction when the `app` directory is used
});
});

test('should create a static transaction when the `app` directory is used and the route is not parameterized', async ({
page,
}) => {
test('should create a transaction named after the static route when the `app` directory is used', async ({ page }) => {
const transactionPromise = waitForTransaction('nextjs-15', async transactionEvent => {
return (
transactionEvent.transaction === '/parameterized/static' && transactionEvent.contexts?.trace?.op === 'pageload'
Expand All @@ -63,9 +61,10 @@ test('should create a static transaction when the `app` directory is used and th
data: {
'sentry.op': 'pageload',
'sentry.origin': 'auto.pageload.nextjs.app_router_instrumentation',
'sentry.source': 'url',
'sentry.source': 'route',
'url.full': expect.stringMatching(/^https?:\/\/localhost:\d+\/parameterized\/static$/),
'url.path': '/parameterized/static',
'url.template': '/parameterized/static',
},
op: 'pageload',
origin: 'auto.pageload.nextjs.app_router_instrumentation',
Expand All @@ -81,11 +80,9 @@ test('should create a static transaction when the `app` directory is used and th
start_timestamp: expect.any(Number),
timestamp: expect.any(Number),
transaction: '/parameterized/static',
transaction_info: { source: 'url' },
transaction_info: { source: 'route' },
type: 'transaction',
});

expect(transaction.contexts?.trace?.data).not.toHaveProperty('url.template');
});

test('should create a partially parameterized transaction when the `app` directory is used', async ({ page }) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,7 @@ test('should create a parameterized transaction when the `app` directory is used
});
});

test('should create a static transaction when the `app` directory is used and the route is not parameterized', async ({
page,
}) => {
test('should create a transaction named after the static route when the `app` directory is used', async ({ page }) => {
const transactionPromise = waitForTransaction('nextjs-16-bun', async transactionEvent => {
return (
transactionEvent.transaction === '/parameterized/static' && transactionEvent.contexts?.trace?.op === 'pageload'
Expand All @@ -60,7 +58,8 @@ test('should create a static transaction when the `app` directory is used and th
data: {
'sentry.op': 'pageload',
'sentry.origin': 'auto.pageload.nextjs.app_router_instrumentation',
'sentry.source': 'url',
'sentry.source': 'route',
'url.template': '/parameterized/static',
},
op: 'pageload',
origin: 'auto.pageload.nextjs.app_router_instrumentation',
Expand All @@ -76,7 +75,7 @@ test('should create a static transaction when the `app` directory is used and th
start_timestamp: expect.any(Number),
timestamp: expect.any(Number),
transaction: '/parameterized/static',
transaction_info: { source: 'url' },
transaction_info: { source: 'route' },
type: 'transaction',
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,7 @@ test('should create a parameterized transaction when the `app` directory is used
});
});

test('should create a static transaction when the `app` directory is used and the route is not parameterized', async ({
page,
}) => {
test('should create a transaction named after the static route when the `app` directory is used', async ({ page }) => {
const transactionPromise = waitForTransaction('nextjs-16-cf-workers', async transactionEvent => {
return (
transactionEvent.transaction === '/parameterized/static' && transactionEvent.contexts?.trace?.op === 'pageload'
Expand All @@ -60,7 +58,8 @@ test('should create a static transaction when the `app` directory is used and th
data: {
'sentry.op': 'pageload',
'sentry.origin': 'auto.pageload.nextjs.app_router_instrumentation',
'sentry.source': 'url',
'sentry.source': 'route',
'url.template': '/parameterized/static',
},
op: 'pageload',
origin: 'auto.pageload.nextjs.app_router_instrumentation',
Expand All @@ -76,7 +75,7 @@ test('should create a static transaction when the `app` directory is used and th
start_timestamp: expect.any(Number),
timestamp: expect.any(Number),
transaction: '/parameterized/static',
transaction_info: { source: 'url' },
transaction_info: { source: 'route' },
type: 'transaction',
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ test('Prerendered shell does not stitch the pageload onto a stale trace', async
});

const pageloadSpanPromise = waitForStreamedSpan('nextjs-16-streaming-cacheComponents', span => {
return span.name === 'Pageload' && getSpanOp(span) === 'pageload' && span.is_segment;
return span.name === '/pageload-tracing' && getSpanOp(span) === 'pageload' && span.is_segment;
});

await page.goto('/pageload-tracing');
Expand All @@ -81,8 +81,9 @@ test('Prerendered shell does not stitch the pageload onto a stale trace', async
const [serverSpan, pageloadSpan] = await Promise.all([serverSpanPromise, pageloadSpanPromise]);

expect(pageloadSpan.attributes).toMatchObject({
['sentry.segment.name.source']: { value: 'url', type: 'string' },
['sentry.segment.name.source']: { value: 'route', type: 'string' },
['url.path']: { value: '/pageload-tracing', type: 'string' },
['url.template']: { value: '/pageload-tracing', type: 'string' },
});
// Under Cache Components the can be prerendered and rendered in a context detached from the
// runtime server request, so a `sentry-trace` meta tag would carry a stale/unrelated trace. The
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ test('Server and client pageload spans should share the same trace', async ({ pa
});

const pageloadSpanPromise = waitForStreamedSpan('nextjs-16-streaming', span => {
return span.name === 'Pageload' && getSpanOp(span) === 'pageload' && span.is_segment;
return span.name === '/pageload-tracing' && getSpanOp(span) === 'pageload' && span.is_segment;
});

await page.goto(`/pageload-tracing`);
Expand All @@ -17,7 +17,7 @@ test('Server and client pageload spans should share the same trace', async ({ pa
expect(pageloadSpan.trace_id).toBeTruthy();
expect(serverSpan.trace_id).toBe(pageloadSpan.trace_id);
expect(pageloadSpan.attributes).toMatchObject({
['sentry.segment.name.source']: { value: 'url', type: 'string' },
['sentry.segment.name.source']: { value: 'route', type: 'string' },

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Incomplete static route assertions

Low Severity

Flagged because the PR review guidelines ask to assert newly added payload data thoroughly. This test updates sentry.segment.name.source to route for the static /pageload-tracing pageload, but does not assert url.template, which sibling updates in this PR (for example the cacheComponents and parameterized static tests) do cover.

Fix in Cursor Fix in Web

Triggered by project rule: PR Review Guidelines for Cursor Bot

Reviewed by Cursor Bugbot for commit b6f7b8f. Configure here.

['url.path']: { value: '/pageload-tracing', type: 'string' },
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,40 @@ test('should create a parameterized streamed span when the `app` directory is us
expect(span.attributes['sentry.source']?.value).toBe('route');
});

test('should create a static streamed span when the `app` directory is used and the route is not parameterized', async ({
test('should create a streamed span named after the static route when the `app` directory is used', async ({
page,
}) => {
const spanPromise = waitForStreamedSpan('nextjs-16-streaming', span => {
return span.name === 'Pageload' && getSpanOp(span) === 'pageload' && span.is_segment;
return span.name === '/parameterized/static' && getSpanOp(span) === 'pageload' && span.is_segment;
});

await page.goto(`/parameterized/static`);

const span = await spanPromise;

expect(span.name).toBe('/parameterized/static');
expect(span.trace_id).toMatch(/[a-f0-9]{32}/);
expect(span.attributes).toMatchObject({
['sentry.segment.name.source']: { value: 'route', type: 'string' },
['url.template']: { value: '/parameterized/static', type: 'string' },
['url.path']: { value: '/parameterized/static', type: 'string' },
});
});

test('should fall back to a low cardinality span name for routes the manifest does not know', async ({ page }) => {
const spanPromise = waitForStreamedSpan('nextjs-16-streaming', span => {
return span.name === 'Pageload' && getSpanOp(span) === 'pageload' && span.is_segment;
});

await page.goto('/this-route-does-not-exist');

const span = await spanPromise;

expect(span.name).toBe('Pageload');
expect(span.trace_id).toMatch(/[a-f0-9]{32}/);
expect(span.attributes).toMatchObject({
['sentry.segment.name.source']: { value: 'url', type: 'string' },
['url.path']: { value: '/parameterized/static', type: 'string' },
['url.path']: { value: '/this-route-does-not-exist', type: 'string' },

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing negative template assertion

Low Severity

Flagged because the PR review guidelines call out relaxed matchers when a payload field is expected to be absent. This new unknown-route fallback test uses toMatchObject for source: 'url' but never asserts that url.template is missing, even though that attribute is only set when a manifest match exists.

Fix in Cursor Fix in Web

Triggered by project rule: PR Review Guidelines for Cursor Bot

Reviewed by Cursor Bugbot for commit b6f7b8f. Configure here.

});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,15 @@ test('should create a correctly named pageload transaction for a static route',
data: {
'sentry.op': 'pageload',
'sentry.origin': 'auto.pageload.nextjs.app_router_instrumentation',
'sentry.source': 'url',
'sentry.source': 'route',
'url.template': '/static-page',
},
op: 'pageload',
origin: 'auto.pageload.nextjs.app_router_instrumentation',
},
},
transaction: '/static-page',
transaction_info: { source: 'url' },
transaction_info: { source: 'route' },
type: 'transaction',
});
});
Expand Down Expand Up @@ -80,14 +81,15 @@ test('should create a correctly named pageload transaction for a static nested r
data: {
'sentry.op': 'pageload',
'sentry.origin': 'auto.pageload.nextjs.app_router_instrumentation',
'sentry.source': 'url',
'sentry.source': 'route',
'url.template': '/parameterized/static',
},
op: 'pageload',
origin: 'auto.pageload.nextjs.app_router_instrumentation',
},
},
transaction: '/parameterized/static',
transaction_info: { source: 'url' },
transaction_info: { source: 'route' },
type: 'transaction',
});
});
Expand Down Expand Up @@ -134,14 +136,15 @@ test('should create a correctly named pageload transaction for the home page', a
data: {
'sentry.op': 'pageload',
'sentry.origin': 'auto.pageload.nextjs.app_router_instrumentation',
'sentry.source': 'url',
'sentry.source': 'route',
'url.template': '/',
},
op: 'pageload',
origin: 'auto.pageload.nextjs.app_router_instrumentation',
},
},
transaction: '/',
transaction_info: { source: 'url' },
transaction_info: { source: 'route' },
type: 'transaction',
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,7 @@ test('should create a parameterized transaction when the `app` directory is used
});
});

test('should create a static transaction when the `app` directory is used and the route is not parameterized', async ({
page,
}) => {
test('should create a transaction named after the static route when the `app` directory is used', async ({ page }) => {
const transactionPromise = waitForTransaction('nextjs-16', async transactionEvent => {
return (
transactionEvent.transaction === '/parameterized/static' && transactionEvent.contexts?.trace?.op === 'pageload'
Expand All @@ -63,9 +61,10 @@ test('should create a static transaction when the `app` directory is used and th
data: {
'sentry.op': 'pageload',
'sentry.origin': 'auto.pageload.nextjs.app_router_instrumentation',
'sentry.source': 'url',
'sentry.source': 'route',
'url.full': expect.stringMatching(/^https?:\/\/localhost:\d+\/parameterized\/static$/),
'url.path': '/parameterized/static',
'url.template': '/parameterized/static',
},
op: 'pageload',
origin: 'auto.pageload.nextjs.app_router_instrumentation',
Expand All @@ -81,11 +80,9 @@ test('should create a static transaction when the `app` directory is used and th
start_timestamp: expect.any(Number),
timestamp: expect.any(Number),
transaction: '/parameterized/static',
transaction_info: { source: 'url' },
transaction_info: { source: 'route' },
type: 'transaction',
});

expect(transaction.contexts?.trace?.data).not.toHaveProperty('url.template');
});

test('should create a partially parameterized transaction when the `app` directory is used', async ({ page }) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ test('Sends a pageload transaction', async ({ page }) => {
expect(transactionEvent).toEqual(
expect.objectContaining({
transaction: '/',
transaction_info: { source: 'url' },
transaction_info: { source: 'route' },
type: 'transaction',
contexts: expect.objectContaining({
react: {
Expand All @@ -37,9 +37,10 @@ test('Sends a pageload transaction', async ({ page }) => {
data: expect.objectContaining({
'sentry.op': 'pageload',
'sentry.origin': 'auto.pageload.nextjs.app_router_instrumentation',
'sentry.source': 'url',
'sentry.source': 'route',
'url.full': expect.stringMatching(/^https?:\/\/localhost:\d+\/$/),
'url.path': '/',
'url.template': '/',
}),
},
}),
Expand All @@ -51,8 +52,6 @@ test('Sends a pageload transaction', async ({ page }) => {
},
}),
);

expect(transactionEvent.contexts?.trace?.data).not.toHaveProperty('url.template');
});

test('Should send a transaction for instrumented server actions', async ({ page }) => {
Expand Down
4 changes: 2 additions & 2 deletions packages/nextjs/src/client/routing/parameterization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,9 +130,9 @@ function findMatchingRoutes(
): string[] {
const matches: string[] = [];

// Static path: no parameterization needed, return empty array
// Static path: no parameterization needed, return the route itself as already parameterized
if (staticRoutes.some(r => r.path === route)) {
return matches;
return [route];
}

// Dynamic path: find the route pattern that matches the concrete route
Expand Down
Loading
Loading