Skip to content

Commit 7505778

Browse files
chargomemsonnb
authored andcommitted
feat(nextjs): Client-side parameterized routes (#16934)
1 parent 01d8c5a commit 7505778

File tree

27 files changed

+1678
-37
lines changed

27 files changed

+1678
-37
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export default function ParameterizedPage() {
2+
return <div>Dynamic page two</div>;
3+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export default function BeepPage() {
2+
return <div>Beep</div>;
3+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export default function ParameterizedPage() {
2+
return <div>Dynamic page one</div>;
3+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export default function StaticPage() {
2+
return (
3+
<div>
4+
Static page
5+
</div>
6+
);
7+
}
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
import { expect, test } from '@playwright/test';
2+
import { waitForTransaction } from '@sentry-internal/test-utils';
3+
4+
test('should create a parameterized transaction when the `app` directory is used', async ({ page }) => {
5+
const transactionPromise = waitForTransaction('nextjs-13', async transactionEvent => {
6+
return (
7+
transactionEvent.transaction === '/parameterized/:one' && transactionEvent.contexts?.trace?.op === 'pageload'
8+
);
9+
});
10+
11+
await page.goto(`/parameterized/cappuccino`);
12+
13+
const transaction = await transactionPromise;
14+
15+
expect(transaction).toMatchObject({
16+
breadcrumbs: expect.arrayContaining([
17+
{
18+
category: 'navigation',
19+
data: { from: '/parameterized/cappuccino', to: '/parameterized/cappuccino' },
20+
timestamp: expect.any(Number),
21+
},
22+
]),
23+
contexts: {
24+
react: { version: expect.any(String) },
25+
trace: {
26+
data: {
27+
'sentry.op': 'pageload',
28+
'sentry.origin': 'auto.pageload.nextjs.app_router_instrumentation',
29+
'sentry.source': 'route',
30+
},
31+
op: 'pageload',
32+
origin: 'auto.pageload.nextjs.app_router_instrumentation',
33+
span_id: expect.stringMatching(/[a-f0-9]{16}/),
34+
trace_id: expect.stringMatching(/[a-f0-9]{32}/),
35+
},
36+
},
37+
environment: 'qa',
38+
request: {
39+
headers: expect.any(Object),
40+
url: expect.stringMatching(/\/parameterized\/cappuccino$/),
41+
},
42+
start_timestamp: expect.any(Number),
43+
timestamp: expect.any(Number),
44+
transaction: '/parameterized/:one',
45+
transaction_info: { source: 'route' },
46+
type: 'transaction',
47+
});
48+
});
49+
50+
test('should create a static transaction when the `app` directory is used and the route is not parameterized', async ({
51+
page,
52+
}) => {
53+
const transactionPromise = waitForTransaction('nextjs-13', async transactionEvent => {
54+
return (
55+
transactionEvent.transaction === '/parameterized/static' && transactionEvent.contexts?.trace?.op === 'pageload'
56+
);
57+
});
58+
59+
await page.goto(`/parameterized/static`);
60+
61+
const transaction = await transactionPromise;
62+
63+
expect(transaction).toMatchObject({
64+
breadcrumbs: expect.arrayContaining([
65+
{
66+
category: 'navigation',
67+
data: { from: '/parameterized/static', to: '/parameterized/static' },
68+
timestamp: expect.any(Number),
69+
},
70+
]),
71+
contexts: {
72+
react: { version: expect.any(String) },
73+
trace: {
74+
data: {
75+
'sentry.op': 'pageload',
76+
'sentry.origin': 'auto.pageload.nextjs.app_router_instrumentation',
77+
'sentry.source': 'url',
78+
},
79+
op: 'pageload',
80+
origin: 'auto.pageload.nextjs.app_router_instrumentation',
81+
span_id: expect.stringMatching(/[a-f0-9]{16}/),
82+
trace_id: expect.stringMatching(/[a-f0-9]{32}/),
83+
},
84+
},
85+
environment: 'qa',
86+
request: {
87+
headers: expect.any(Object),
88+
url: expect.stringMatching(/\/parameterized\/static$/),
89+
},
90+
start_timestamp: expect.any(Number),
91+
timestamp: expect.any(Number),
92+
transaction: '/parameterized/static',
93+
transaction_info: { source: 'url' },
94+
type: 'transaction',
95+
});
96+
});
97+
98+
test('should create a partially parameterized transaction when the `app` directory is used', async ({ page }) => {
99+
const transactionPromise = waitForTransaction('nextjs-13', async transactionEvent => {
100+
return (
101+
transactionEvent.transaction === '/parameterized/:one/beep' && transactionEvent.contexts?.trace?.op === 'pageload'
102+
);
103+
});
104+
105+
await page.goto(`/parameterized/cappuccino/beep`);
106+
107+
const transaction = await transactionPromise;
108+
109+
expect(transaction).toMatchObject({
110+
breadcrumbs: expect.arrayContaining([
111+
{
112+
category: 'navigation',
113+
data: { from: '/parameterized/cappuccino/beep', to: '/parameterized/cappuccino/beep' },
114+
timestamp: expect.any(Number),
115+
},
116+
]),
117+
contexts: {
118+
react: { version: expect.any(String) },
119+
trace: {
120+
data: {
121+
'sentry.op': 'pageload',
122+
'sentry.origin': 'auto.pageload.nextjs.app_router_instrumentation',
123+
'sentry.source': 'route',
124+
},
125+
op: 'pageload',
126+
origin: 'auto.pageload.nextjs.app_router_instrumentation',
127+
span_id: expect.stringMatching(/[a-f0-9]{16}/),
128+
trace_id: expect.stringMatching(/[a-f0-9]{32}/),
129+
},
130+
},
131+
environment: 'qa',
132+
request: {
133+
headers: expect.any(Object),
134+
url: expect.stringMatching(/\/parameterized\/cappuccino\/beep$/),
135+
},
136+
start_timestamp: expect.any(Number),
137+
timestamp: expect.any(Number),
138+
transaction: '/parameterized/:one/beep',
139+
transaction_info: { source: 'route' },
140+
type: 'transaction',
141+
});
142+
});
143+
144+
test('should create a nested parameterized transaction when the `app` directory is used', async ({ page }) => {
145+
const transactionPromise = waitForTransaction('nextjs-13', async transactionEvent => {
146+
return (
147+
transactionEvent.transaction === '/parameterized/:one/beep/:two' &&
148+
transactionEvent.contexts?.trace?.op === 'pageload'
149+
);
150+
});
151+
152+
await page.goto(`/parameterized/cappuccino/beep/espresso`);
153+
154+
const transaction = await transactionPromise;
155+
156+
expect(transaction).toMatchObject({
157+
breadcrumbs: expect.arrayContaining([
158+
{
159+
category: 'navigation',
160+
data: { from: '/parameterized/cappuccino/beep/espresso', to: '/parameterized/cappuccino/beep/espresso' },
161+
timestamp: expect.any(Number),
162+
},
163+
]),
164+
contexts: {
165+
react: { version: expect.any(String) },
166+
trace: {
167+
data: {
168+
'sentry.op': 'pageload',
169+
'sentry.origin': 'auto.pageload.nextjs.app_router_instrumentation',
170+
'sentry.source': 'route',
171+
},
172+
op: 'pageload',
173+
origin: 'auto.pageload.nextjs.app_router_instrumentation',
174+
span_id: expect.stringMatching(/[a-f0-9]{16}/),
175+
trace_id: expect.stringMatching(/[a-f0-9]{32}/),
176+
},
177+
},
178+
environment: 'qa',
179+
request: {
180+
headers: expect.any(Object),
181+
url: expect.stringMatching(/\/parameterized\/cappuccino\/beep\/espresso$/),
182+
},
183+
start_timestamp: expect.any(Number),
184+
timestamp: expect.any(Number),
185+
transaction: '/parameterized/:one/beep/:two',
186+
transaction_info: { source: 'route' },
187+
type: 'transaction',
188+
});
189+
});
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export default function ParameterizedPage() {
2+
return <div>Dynamic page two</div>;
3+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export default function BeepPage() {
2+
return <div>Beep</div>;
3+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export default function ParameterizedPage() {
2+
return <div>Dynamic page one</div>;
3+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export default function StaticPage() {
2+
return (
3+
<div>
4+
Static page
5+
</div>
6+
);
7+
}

0 commit comments

Comments
 (0)