Skip to content

meta(changelog): Update changelog for 9.39.0 #17020

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 38 commits into from
Jul 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
193e614
Merge pull request #16932 from getsentry/master
github-actions[bot] Jul 11, 2025
422443e
test: Exclude `node-core-integration-tests` from Node Unit tests CI j…
Lms24 Jul 11, 2025
7a0232d
feat(nextjs): Build app manifest (#16851)
chargome Jul 11, 2025
0fc803a
ref(sveltekit): Use `debug` in sveltekit sdk (#16914)
AbhiPrasad Jul 11, 2025
9ec2bff
build: Disable side-effects for any `./debug-build.ts` file (#16929)
timfish Jul 11, 2025
baef982
fix(core): Remove side-effect from `tracing/errors.ts` (#16888)
timfish Jul 11, 2025
101b4f2
feat(core): Prepend vercel ai attributes with `vercel.ai.X` (#16908)
AbhiPrasad Jul 11, 2025
2912474
feat(browser): Add `afterStartPageloadSpan` hook to improve spanId as…
Lms24 Jul 11, 2025
369e5c0
ref(react): Use `debug` instead of `logger` (#16958)
AbhiPrasad Jul 11, 2025
5c3175f
ref(core): Avoid side-effect of `vercelAiEventProcessor` (#16925)
mydea Jul 14, 2025
61feeb3
ref(core): Keep client-logger map on carrier & avoid side-effect (#16…
mydea Jul 14, 2025
65162a2
fix(core): Avoid prolonging idle span when starting standalone span (…
Lms24 Jul 14, 2025
cebf518
docs(aws-serverless): Fix package homepage link (#16979)
janpapenbrock Jul 14, 2025
bcb7422
ref(vercel-edge): Use `debug` in vercel edge sdk (#16912)
AbhiPrasad Jul 14, 2025
e727db4
fix(core): Wrap `beforeSendLog` in `consoleSandbox` (#16968)
AbhiPrasad Jul 14, 2025
17daa16
ref(node-native): Use `debug` instead of `logger` (#16956)
AbhiPrasad Jul 14, 2025
6f07e5a
chore: Use volta when running E2E tests locally (#16983)
mydea Jul 14, 2025
cc8cd7e
chore: Add external contributor to CHANGELOG.md (#16980)
HazAT Jul 14, 2025
676fc50
feat(node-native): Add option to disable event loop blocked detection…
timfish Jul 14, 2025
2426c9a
ref(replay-internal): Use `debug` instead of `logger` (#16987)
AbhiPrasad Jul 14, 2025
47f85b9
feat(node): Drop 401-404 and 3xx status code spans by default (#16972)
mydea Jul 15, 2025
16d95cd
feat(nextjs): Inject manifest into client for webpack builds (#16857)
chargome Jul 15, 2025
a3b4501
ref(nextjs): Allow `rollup@^4.35.0` dependency range (#17010)
Lms24 Jul 15, 2025
50120cb
ref(core): Avoid keeping span-FF map on GLOBAL_OBJ (#16924)
mydea Jul 15, 2025
e67ba06
feat(nextjs): Inject manifest into client for turbopack builds (#16902)
chargome Jul 15, 2025
4c1079e
fix(react-router): Ensure that all browser spans have `source=route` …
mydea Jul 15, 2025
d23207c
feat(nextjs): Client-side parameterized routes (#16934)
chargome Jul 15, 2025
fa210ad
ref(node): Add `sentry.parent_span_already_sent` attribute (#16870)
mydea Jul 15, 2025
10d5454
feat(nextjs): Add `disableSentryWebpackConfig` flag (#17013)
chargome Jul 15, 2025
90c2385
fix(node-core): Apply correct SDK metadata (#17014)
andreiborza Jul 15, 2025
6e2eaed
feat(react-router): Ensure http.server route handling is consistent (…
mydea Jul 15, 2025
b0623d8
chore(node-core): Add node-core to release-registry in .craft.yml (#1…
andreiborza Jul 15, 2025
d8faaa2
ref(solidstart): Use `debug` instead of `logger` (#16936)
AbhiPrasad Jul 15, 2025
92e55a9
ref(pino-transport): Use `debug` instead of `logger` (#16957)
AbhiPrasad Jul 15, 2025
e07cdfd
ref(profiling-node): Use `debug` instead of `logger` (#16959)
AbhiPrasad Jul 15, 2025
5f45a48
ref(remix): Use `debug` instead of `logger` (#16988)
AbhiPrasad Jul 15, 2025
c23fa70
ref(nuxt): Use `debug` instead of `logger` (#16991)
AbhiPrasad Jul 15, 2025
fb80b36
meta(changelog): Update changelog for 9.39.0
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
2 changes: 2 additions & 0 deletions .craft.yml
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,8 @@ targets:
onlyIfPresent: /^sentry-nuxt-\d.*\.tgz$/
'npm:@sentry/node':
onlyIfPresent: /^sentry-node-\d.*\.tgz$/
'npm:@sentry/node-core':
onlyIfPresent: /^sentry-node-core-\d.*\.tgz$/
'npm:@sentry/react':
onlyIfPresent: /^sentry-react-\d.*\.tgz$/
'npm:@sentry/react-router':
Expand Down
33 changes: 33 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,39 @@

- "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott

## 9.39.0

### Important Changes

- **feat(browser): Add `afterStartPageloadSpan` hook to improve spanId assignment on web vital spans ([#16893](https://github.com/getsentry/sentry-javascript/pull/16893))**

This PR adds a new afterStartPageloadSpan lifecycle hook to more robustly assign the correct pageload span ID to web vital spans, replacing the previous unreliable "wait for a tick" approach with a direct callback that fires when the pageload span becomes available.

- **feat(nextjs): Client-side parameterized routes ([#16934](https://github.com/getsentry/sentry-javascript/pull/16934))**

This PR implements client-side parameterized routes for Next.js by leveraging an injected manifest within the existing app-router instrumentation to automatically parameterize all client-side transactions (e.g. `users/123` and `users/456` now become become `users/:id`).

- **feat(node): Drop 401-404 and 3xx status code spans by default ([#16972](https://github.com/getsentry/sentry-javascript/pull/16972))**

This PR changes the default behavior in the Node SDK to drop HTTP spans with 401-404 and 3xx status codes by default to reduce noise in tracing data.

### Other Changes

- feat(core): Prepend vercel ai attributes with `vercel.ai.X` ([#16908](https://github.com/getsentry/sentry-javascript/pull/16908))
- feat(nextjs): Add `disableSentryWebpackConfig` flag ([#17013](https://github.com/getsentry/sentry-javascript/pull/17013))
- feat(nextjs): Build app manifest ([#16851](https://github.com/getsentry/sentry-javascript/pull/16851))
- feat(nextjs): Inject manifest into client for turbopack builds ([#16902](https://github.com/getsentry/sentry-javascript/pull/16902))
- feat(nextjs): Inject manifest into client for webpack builds ([#16857](https://github.com/getsentry/sentry-javascript/pull/16857))
- feat(node-native): Add option to disable event loop blocked detection ([#16919](https://github.com/getsentry/sentry-javascript/pull/16919))
- feat(react-router): Ensure http.server route handling is consistent ([#16986](https://github.com/getsentry/sentry-javascript/pull/16986))
- fix(core): Avoid prolonging idle span when starting standalone span ([#16928](https://github.com/getsentry/sentry-javascript/pull/16928))
- fix(core): Remove side-effect from `tracing/errors.ts` ([#16888](https://github.com/getsentry/sentry-javascript/pull/16888))
- fix(core): Wrap `beforeSendLog` in `consoleSandbox` ([#16968](https://github.com/getsentry/sentry-javascript/pull/16968))
- fix(node-core): Apply correct SDK metadata ([#17014](https://github.com/getsentry/sentry-javascript/pull/17014))
- fix(react-router): Ensure that all browser spans have `source=route` ([#16984](https://github.com/getsentry/sentry-javascript/pull/16984))

Work in this release was contributed by @janpapenbrock. Thank you for your contribution!

## 9.38.0

### Important Changes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ sentryTest('should output logger messages', async ({ getLocalTestUrl, page }) =>
await Promise.all([page.goto(url), reqPromise0]);

expect(messages).toContain('Sentry Logger [log]: Integration installed: Replay');
expect(messages).toContain('Sentry Logger [info]: [Replay] Creating new session');
expect(messages).toContain('Sentry Logger [info]: [Replay] Starting replay in session mode');
expect(messages).toContain('Sentry Logger [info]: [Replay] Using compression worker');
expect(messages).toContain('Sentry Logger [log]: [Replay] Creating new session');
expect(messages).toContain('Sentry Logger [log]: [Replay] Starting replay in session mode');
expect(messages).toContain('Sentry Logger [log]: [Replay] Using compression worker');
});
4 changes: 2 additions & 2 deletions dev-packages/e2e-tests/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,10 @@ async function run(): Promise<void> {
const cwd = tmpDirPath;

console.log(`Building ${testAppPath} in ${tmpDirPath}...`);
await asyncExec('pnpm test:build', { env, cwd });
await asyncExec('volta run pnpm test:build', { env, cwd });

console.log(`Testing ${testAppPath}...`);
await asyncExec('pnpm test:assert', { env, cwd });
await asyncExec('volta run pnpm test:assert', { env, cwd });

// clean up (although this is tmp, still nice to do)
await rm(tmpDirPath, { recursive: true });
Expand Down
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',
});
});

This file was deleted.

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
Expand Up @@ -108,20 +108,3 @@ test('Should send a transaction and an error event for a faulty generateViewport

expect(errorEvent.transaction).toBe('Page.generateViewport (/generation-functions)');
});

test('Should send a transaction event with correct status for a generateMetadata() function invocation with redirect()', async ({
page,
}) => {
const testTitle = 'redirect-foobar';

const transactionPromise = waitForTransaction('nextjs-14', async transactionEvent => {
return (
transactionEvent.contexts?.trace?.data?.['http.target'] ===
`/generation-functions/with-redirect?metadataTitle=${testTitle}`
);
});

await page.goto(`/generation-functions/with-redirect?metadataTitle=${testTitle}`);

expect((await transactionPromise).contexts?.trace?.status).toBe('ok');
});
Loading
Loading