Skip to content

Commit 91652f4

Browse files
author
Luca Forstner
committed
feat(nextjs): Instrument SSR page components
1 parent a0ff516 commit 91652f4

File tree

4 files changed

+74
-1
lines changed

4 files changed

+74
-1
lines changed

packages/nextjs/src/common/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,5 @@ export { wrapRouteHandlerWithSentry } from './wrapRouteHandlerWithSentry';
4141
export { wrapApiHandlerWithSentryVercelCrons } from './wrapApiHandlerWithSentryVercelCrons';
4242

4343
export { wrapMiddlewareWithSentry } from './wrapMiddlewareWithSentry';
44+
45+
export { wrapPageComponentWithSentry } from './wrapPageComponentWithSentry';
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { captureException } from '@sentry/core';
2+
import { addExceptionMechanism } from '@sentry/utils';
3+
4+
interface FunctionComponent {
5+
(...args: unknown[]): unknown;
6+
}
7+
8+
interface ClassComponent {
9+
new (...args: unknown[]): {
10+
render(...args: unknown[]): unknown;
11+
};
12+
}
13+
14+
function isReactClassComponent(target: unknown): target is ClassComponent {
15+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
16+
return typeof target === 'function' && target?.prototype?.isReactComponent;
17+
}
18+
19+
/**
20+
* Wraps a page component with Sentry error instrumentation.
21+
*/
22+
export function wrapPageComponentWithSentry(pageComponent: FunctionComponent | ClassComponent): unknown {
23+
if (isReactClassComponent(pageComponent)) {
24+
return class SentryWrappedPageComponent extends pageComponent {
25+
public render(...args: unknown[]): unknown {
26+
try {
27+
return super.render(...args);
28+
} catch (e) {
29+
captureException(e, scope => {
30+
scope.addEventProcessor(event => {
31+
addExceptionMechanism(event, {
32+
handled: false,
33+
});
34+
return event;
35+
});
36+
37+
return scope;
38+
});
39+
throw e;
40+
}
41+
}
42+
};
43+
} else if (typeof pageComponent === 'function') {
44+
return new Proxy(pageComponent, {
45+
apply(target, thisArg, argArray) {
46+
try {
47+
return target.apply(thisArg, argArray);
48+
} catch (e) {
49+
captureException(e, scope => {
50+
scope.addEventProcessor(event => {
51+
addExceptionMechanism(event, {
52+
handled: false,
53+
});
54+
return event;
55+
});
56+
57+
return scope;
58+
});
59+
throw e;
60+
}
61+
},
62+
});
63+
} else {
64+
return pageComponent;
65+
}
66+
}

packages/nextjs/src/config/templates/pageWrapperTemplate.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ export const getServerSideProps =
4949
? Sentry.wrapGetServerSidePropsWithSentry(origGetServerSideProps, '__ROUTE__')
5050
: undefined;
5151

52-
export default pageComponent;
52+
export default pageComponent ? Sentry.wrapPageComponentWithSentry(pageComponent as unknown) : pageComponent;
5353

5454
// Re-export anything exported by the page module we're wrapping. When processing this code, Rollup is smart enough to
5555
// not include anything whose name matchs something we've explicitly exported above.

packages/nextjs/src/index.types.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,3 +186,8 @@ export declare function wrapApiHandlerWithSentryVercelCrons<F extends (...args:
186186
WrappingTarget: F,
187187
vercelCronsConfig: VercelCronsConfig,
188188
): F;
189+
190+
/**
191+
* Wraps a page component with Sentry error instrumentation.
192+
*/
193+
export declare function wrapPageComponentWithSentry<C>(WrappingTarget: C): C;

0 commit comments

Comments
 (0)