Skip to content

Commit

Permalink
feat(nextjs): Improve client stack traces (#7097)
Browse files Browse the repository at this point in the history
  • Loading branch information
lforst authored Feb 9, 2023
1 parent fc7b716 commit 538c3a6
Show file tree
Hide file tree
Showing 3 changed files with 70 additions and 1 deletion.
16 changes: 16 additions & 0 deletions packages/nextjs/src/client/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,22 @@ function addClientIntegrations(options: BrowserOptions): void {
// Filename wasn't a properly formed URL, so there's nothing we can do
}

if (frame.filename && frame.filename.startsWith('app:///_next')) {
// We need to URI-decode the filename because Next.js has wildcard routes like "/users/[id].js" which show up as "/users/%5id%5.js" in Error stacktraces.
// The corresponding sources that Next.js generates have proper brackets so we also need proper brackets in the frame so that source map resolving works.
frame.filename = decodeURI(frame.filename);
}

if (
frame.filename &&
frame.filename.match(
/^app:\/\/\/_next\/static\/chunks\/(main-|main-app-|polyfills-|webpack-|framework-|framework\.)[0-9a-f]+\.js$/,
)
) {
// We don't care about these frames. It's Next.js internal code.
frame.in_app = false;
}

return frame;
},
});
Expand Down
11 changes: 11 additions & 0 deletions packages/nextjs/test/integration/pages/[id]/errorClick.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
const ButtonPage = (): JSX.Element => (
<button
onClick={() => {
throw new Error('Sentry Frontend Error');
}}
>
Throw Error
</button>
);

export default ButtonPage;
44 changes: 43 additions & 1 deletion packages/nextjs/test/integration/test/client/errorClick.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Event } from '@sentry/types';
test('should capture error triggered on click', async ({ page }) => {
await page.goto('/errorClick');

const [_, events] = await Promise.all([
const [, events] = await Promise.all([
page.click('button'),
getMultipleSentryEnvelopeRequests<Event>(page, 1, { envelopeType: 'event' }),
]);
Expand All @@ -15,3 +15,45 @@ test('should capture error triggered on click', async ({ page }) => {
value: 'Sentry Frontend Error',
});
});

test('should have a non-url-encoded top frame in route with parameter', async ({ page }) => {
await page.goto('/some-param/errorClick');

const [, events] = await Promise.all([
page.click('button'),
getMultipleSentryEnvelopeRequests<Event>(page, 1, { envelopeType: 'event' }),
]);

const frames = events[0]?.exception?.values?.[0].stacktrace?.frames;

expect(frames?.[frames.length - 1].filename).toMatch(/\/\[id\]\/errorClick-[a-f0-9]+\.js$/);
});

test('should mark nextjs internal frames as `in_app`: false', async ({ page }) => {
await page.goto('/some-param/errorClick');

const [, events] = await Promise.all([
page.click('button'),
getMultipleSentryEnvelopeRequests<Event>(page, 1, { envelopeType: 'event' }),
]);

const frames = events[0]?.exception?.values?.[0].stacktrace?.frames;

expect(frames).toContainEqual(
expect.objectContaining({
filename: expect.stringMatching(
/^app:\/\/\/_next\/static\/chunks\/(main-|main-app-|polyfills-|webpack-|framework-|framework\.)[0-9a-f]+\.js$/,
),
in_app: false,
}),
);

expect(frames).not.toContainEqual(
expect.objectContaining({
filename: expect.stringMatching(
/^app:\/\/\/_next\/static\/chunks\/(main-|main-app-|polyfills-|webpack-|framework-|framework\.)[0-9a-f]+\.js$/,
),
in_app: true,
}),
);
});

0 comments on commit 538c3a6

Please sign in to comment.