Skip to content

Commit

Permalink
feat(nextjs): Instrument SSR page components
Browse files Browse the repository at this point in the history
  • Loading branch information
lforst committed Oct 25, 2023
1 parent a0ff516 commit 91652f4
Show file tree
Hide file tree
Showing 4 changed files with 74 additions and 1 deletion.
2 changes: 2 additions & 0 deletions packages/nextjs/src/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,5 @@ export { wrapRouteHandlerWithSentry } from './wrapRouteHandlerWithSentry';
export { wrapApiHandlerWithSentryVercelCrons } from './wrapApiHandlerWithSentryVercelCrons';

export { wrapMiddlewareWithSentry } from './wrapMiddlewareWithSentry';

export { wrapPageComponentWithSentry } from './wrapPageComponentWithSentry';
66 changes: 66 additions & 0 deletions packages/nextjs/src/common/wrapPageComponentWithSentry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { captureException } from '@sentry/core';
import { addExceptionMechanism } from '@sentry/utils';

interface FunctionComponent {
(...args: unknown[]): unknown;
}

interface ClassComponent {
new (...args: unknown[]): {
render(...args: unknown[]): unknown;
};
}

function isReactClassComponent(target: unknown): target is ClassComponent {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
return typeof target === 'function' && target?.prototype?.isReactComponent;
}

/**
* Wraps a page component with Sentry error instrumentation.
*/
export function wrapPageComponentWithSentry(pageComponent: FunctionComponent | ClassComponent): unknown {
if (isReactClassComponent(pageComponent)) {
return class SentryWrappedPageComponent extends pageComponent {
public render(...args: unknown[]): unknown {
try {
return super.render(...args);
} catch (e) {
captureException(e, scope => {
scope.addEventProcessor(event => {
addExceptionMechanism(event, {
handled: false,
});
return event;
});

return scope;
});
throw e;
}
}
};
} else if (typeof pageComponent === 'function') {
return new Proxy(pageComponent, {
apply(target, thisArg, argArray) {
try {
return target.apply(thisArg, argArray);
} catch (e) {
captureException(e, scope => {
scope.addEventProcessor(event => {
addExceptionMechanism(event, {
handled: false,
});
return event;
});

return scope;
});
throw e;
}
},
});
} else {
return pageComponent;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export const getServerSideProps =
? Sentry.wrapGetServerSidePropsWithSentry(origGetServerSideProps, '__ROUTE__')
: undefined;

export default pageComponent;
export default pageComponent ? Sentry.wrapPageComponentWithSentry(pageComponent as unknown) : pageComponent;

// Re-export anything exported by the page module we're wrapping. When processing this code, Rollup is smart enough to
// not include anything whose name matchs something we've explicitly exported above.
Expand Down
5 changes: 5 additions & 0 deletions packages/nextjs/src/index.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,3 +186,8 @@ export declare function wrapApiHandlerWithSentryVercelCrons<F extends (...args:
WrappingTarget: F,
vercelCronsConfig: VercelCronsConfig,
): F;

/**
* Wraps a page component with Sentry error instrumentation.
*/
export declare function wrapPageComponentWithSentry<C>(WrappingTarget: C): C;

0 comments on commit 91652f4

Please sign in to comment.