Skip to content

feat(nextjs): Client-side parameterized routes #16934

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 46 commits into from
Jul 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
73e0637
build manifest function
chargome Jul 8, 2025
9b28d79
tests
chargome Jul 8, 2025
657dd55
rename file
chargome Jul 8, 2025
a82b977
simplify page check
chargome Jul 8, 2025
7d6e198
que
chargome Jul 9, 2025
71b1121
fix handling optional catchall routes
chargome Jul 9, 2025
3114f5c
update if else block
chargome Jul 9, 2025
75305f5
..
chargome Jul 9, 2025
4ad310f
inject manifest
chargome Jul 9, 2025
e105620
update interface + rename func
chargome Jul 9, 2025
685bf04
Merge branch 'cg-next-client-manifest' into cg-next-manifest-webpack
chargome Jul 9, 2025
f593c49
Merge branch 'develop' into cg-next-client-manifest
chargome Jul 9, 2025
2a4a35c
Merge branch 'cg-next-client-manifest' into cg-next-manifest-webpack
chargome Jul 9, 2025
f9430fe
rename option
chargome Jul 9, 2025
4c0d008
add valueinjection to turbopack
chargome Jul 10, 2025
de44fe0
merge stuff
chargome Jul 10, 2025
6071f76
split routes again
chargome Jul 11, 2025
e416a2c
add option for route-groups
chargome Jul 11, 2025
78ff933
Merge branch 'cg-next-client-manifest' into cg-next-manifest-webpack
chargome Jul 11, 2025
06be02e
Merge branch 'cg-next-manifest-webpack' into cg-next-manifest-turbopack
chargome Jul 11, 2025
109b8d0
whoop whoop
chargome Jul 11, 2025
aeb2e6d
Merge branch 'develop' into cg-next-manifest-webpack
chargome Jul 11, 2025
1d9089f
Merge branch 'cg-next-manifest-webpack' into cg-next-manifest-turbopack
chargome Jul 11, 2025
b80c7e6
Merge branch 'cg-next-manifest-turbopack' into cg-next-client-paramet…
chargome Jul 11, 2025
b9417bc
add parse and checks
chargome Jul 13, 2025
d194ebc
add tests for nextjs-13
chargome Jul 13, 2025
5a2294a
add tests for nextjs-14
chargome Jul 13, 2025
e3220d9
test test test nextjs-15
chargome Jul 13, 2025
29e7235
update turbo tests
chargome Jul 13, 2025
060a3bd
actually use stringified manifest
chargome Jul 13, 2025
97fc38e
handle catchall edge case
chargome Jul 13, 2025
4192c37
update existing router tests
chargome Jul 13, 2025
50cf21a
pls run all tests ?!
chargome Jul 13, 2025
a039505
run next-15 tests sequentially
chargome Jul 13, 2025
b01ddc7
conditionally add turbo
chargome Jul 14, 2025
72a61c4
finding this took about 10 years of my life
chargome Jul 14, 2025
d4f2ba1
Merge branch 'cg-next-manifest-turbopack' into cg-next-client-paramet…
chargome Jul 14, 2025
b62bdb6
Update packages/nextjs/src/config/turbopack/constructTurbopackConfig.ts
chargome Jul 14, 2025
16eb903
Update packages/nextjs/src/config/turbopack/constructTurbopackConfig.ts
chargome Jul 14, 2025
f4dbaca
add log for existing matcher
chargome Jul 14, 2025
7159fcb
windows path + update tests
chargome Jul 14, 2025
36322c3
.
chargome Jul 14, 2025
b0321c5
log error
chargome Jul 14, 2025
aafb0a5
optimize performance
chargome Jul 14, 2025
22ba6d2
Merge branch 'cg-next-manifest-turbopack' into cg-next-client-paramet…
chargome Jul 14, 2025
320589e
Merge branch 'develop' into cg-next-client-parameterized-routes
chargome Jul 15, 2025
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
@@ -0,0 +1,3 @@
export default function ParameterizedPage() {
return <div>Dynamic page two</div>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function BeepPage() {
return <div>Beep</div>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function ParameterizedPage() {
return <div>Dynamic page one</div>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export default function StaticPage() {
return (
<div>
Static page
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
import { expect, test } from '@playwright/test';
import { waitForTransaction } from '@sentry-internal/test-utils';

test('should create a parameterized transaction when the `app` directory is used', async ({ page }) => {
const transactionPromise = waitForTransaction('nextjs-13', async transactionEvent => {
return (
transactionEvent.transaction === '/parameterized/:one' && transactionEvent.contexts?.trace?.op === 'pageload'
);
});

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

const transaction = await transactionPromise;

expect(transaction).toMatchObject({
breadcrumbs: expect.arrayContaining([
{
category: 'navigation',
data: { from: '/parameterized/cappuccino', to: '/parameterized/cappuccino' },
timestamp: expect.any(Number),
},
]),
contexts: {
react: { version: expect.any(String) },
trace: {
data: {
'sentry.op': 'pageload',
'sentry.origin': 'auto.pageload.nextjs.app_router_instrumentation',
'sentry.source': 'route',
},
op: 'pageload',
origin: 'auto.pageload.nextjs.app_router_instrumentation',
span_id: expect.stringMatching(/[a-f0-9]{16}/),
trace_id: expect.stringMatching(/[a-f0-9]{32}/),
},
},
environment: 'qa',
request: {
headers: expect.any(Object),
url: expect.stringMatching(/\/parameterized\/cappuccino$/),
},
start_timestamp: expect.any(Number),
timestamp: expect.any(Number),
transaction: '/parameterized/:one',
transaction_info: { source: 'route' },
type: 'transaction',
});
});

test('should create a static transaction when the `app` directory is used and the route is not parameterized', async ({
page,
}) => {
const transactionPromise = waitForTransaction('nextjs-13', async transactionEvent => {
return (
transactionEvent.transaction === '/parameterized/static' && transactionEvent.contexts?.trace?.op === 'pageload'
);
});

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

const transaction = await transactionPromise;

expect(transaction).toMatchObject({
breadcrumbs: expect.arrayContaining([
{
category: 'navigation',
data: { from: '/parameterized/static', to: '/parameterized/static' },
timestamp: expect.any(Number),
},
]),
contexts: {
react: { version: expect.any(String) },
trace: {
data: {
'sentry.op': 'pageload',
'sentry.origin': 'auto.pageload.nextjs.app_router_instrumentation',
'sentry.source': 'url',
},
op: 'pageload',
origin: 'auto.pageload.nextjs.app_router_instrumentation',
span_id: expect.stringMatching(/[a-f0-9]{16}/),
trace_id: expect.stringMatching(/[a-f0-9]{32}/),
},
},
environment: 'qa',
request: {
headers: expect.any(Object),
url: expect.stringMatching(/\/parameterized\/static$/),
},
start_timestamp: expect.any(Number),
timestamp: expect.any(Number),
transaction: '/parameterized/static',
transaction_info: { source: 'url' },
type: 'transaction',
});
});

test('should create a partially parameterized transaction when the `app` directory is used', async ({ page }) => {
const transactionPromise = waitForTransaction('nextjs-13', async transactionEvent => {
return (
transactionEvent.transaction === '/parameterized/:one/beep' && transactionEvent.contexts?.trace?.op === 'pageload'
);
});

await page.goto(`/parameterized/cappuccino/beep`);

const transaction = await transactionPromise;

expect(transaction).toMatchObject({
breadcrumbs: expect.arrayContaining([
{
category: 'navigation',
data: { from: '/parameterized/cappuccino/beep', to: '/parameterized/cappuccino/beep' },
timestamp: expect.any(Number),
},
]),
contexts: {
react: { version: expect.any(String) },
trace: {
data: {
'sentry.op': 'pageload',
'sentry.origin': 'auto.pageload.nextjs.app_router_instrumentation',
'sentry.source': 'route',
},
op: 'pageload',
origin: 'auto.pageload.nextjs.app_router_instrumentation',
span_id: expect.stringMatching(/[a-f0-9]{16}/),
trace_id: expect.stringMatching(/[a-f0-9]{32}/),
},
},
environment: 'qa',
request: {
headers: expect.any(Object),
url: expect.stringMatching(/\/parameterized\/cappuccino\/beep$/),
},
start_timestamp: expect.any(Number),
timestamp: expect.any(Number),
transaction: '/parameterized/:one/beep',
transaction_info: { source: 'route' },
type: 'transaction',
});
});

test('should create a nested parameterized transaction when the `app` directory is used', async ({ page }) => {
const transactionPromise = waitForTransaction('nextjs-13', async transactionEvent => {
return (
transactionEvent.transaction === '/parameterized/:one/beep/:two' &&
transactionEvent.contexts?.trace?.op === 'pageload'
);
});

await page.goto(`/parameterized/cappuccino/beep/espresso`);

const transaction = await transactionPromise;

expect(transaction).toMatchObject({
breadcrumbs: expect.arrayContaining([
{
category: 'navigation',
data: { from: '/parameterized/cappuccino/beep/espresso', to: '/parameterized/cappuccino/beep/espresso' },
timestamp: expect.any(Number),
},
]),
contexts: {
react: { version: expect.any(String) },
trace: {
data: {
'sentry.op': 'pageload',
'sentry.origin': 'auto.pageload.nextjs.app_router_instrumentation',
'sentry.source': 'route',
},
op: 'pageload',
origin: 'auto.pageload.nextjs.app_router_instrumentation',
span_id: expect.stringMatching(/[a-f0-9]{16}/),
trace_id: expect.stringMatching(/[a-f0-9]{32}/),
},
},
environment: 'qa',
request: {
headers: expect.any(Object),
url: expect.stringMatching(/\/parameterized\/cappuccino\/beep\/espresso$/),
},
start_timestamp: expect.any(Number),
timestamp: expect.any(Number),
transaction: '/parameterized/:one/beep/:two',
transaction_info: { source: 'route' },
type: 'transaction',
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function ParameterizedPage() {
return <div>Dynamic page two</div>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function BeepPage() {
return <div>Beep</div>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function ParameterizedPage() {
return <div>Dynamic page one</div>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export default function StaticPage() {
return (
<div>
Static page
</div>
);
}
Loading
Loading