getsentry / sentry-javascript

Official Sentry SDKs for JavaScript
https://sentry.io
MIT License
7.94k stars 1.56k forks source link

No docs or example for typescript version of custom `_error` page in Next.js #5039

Closed iamchathu closed 2 years ago

iamchathu commented 2 years ago

Is there an existing issue for this?

How do you use Sentry?

Sentry Saas (sentry.io)

Which package are you using?

@sentry/nextjs

SDK Version

6.19.7

Framework Version

12.1.5

Link to Sentry event

No response

Steps to Reproduce

Create new _error.tsx inside pages directory and paste the manual example code.

I have used few types from the next but there are issues.

import * as Sentry from '@sentry/nextjs';
import { NextPageContext } from 'next';
import NextErrorComponent from 'next/error';

const MyError = ({ statusCode, hasGetInitialPropsRun, err }) => {
  if (!hasGetInitialPropsRun && err) {
    // getInitialProps is not called in case of
    // https://github.com/vercel/next.js/issues/8592. As a workaround, we pass
    // err via _app.js so it can be captured
    Sentry.captureException(err);
    // Flushing is not required in this case as it only happens on the client
  }

  return <NextErrorComponent statusCode={statusCode} />;
};

MyError.getInitialProps = async ({ res, err, asPath }: NextPageContext) => {
  const errorInitialProps = await NextErrorComponent.getInitialProps({
    res,
    err,
  });

  // Workaround for https://github.com/vercel/next.js/issues/8592, mark when
  // getInitialProps has run
  errorInitialProps.hasGetInitialPropsRun = true;

  // Running on the server, the response object (`res`) is available.
  //
  // Next.js will pass an err on the server if a page's data fetching methods
  // threw or returned a Promise that rejected
  //
  // Running on the client (browser), Next.js will provide an err if:
  //
  //  - a page's `getInitialProps` threw or returned a Promise that rejected
  //  - an exception was thrown somewhere in the React lifecycle (render,
  //    componentDidMount, etc) that was caught by Next.js's React Error
  //    Boundary. Read more about what types of exceptions are caught by Error
  //    Boundaries: https://reactjs.org/docs/error-boundaries.html

  if (err) {
    Sentry.captureException(err);

    // Flushing before returning is necessary if deploying to Vercel, see
    // https://vercel.com/docs/platform/limits#streaming-responses
    await Sentry.flush(2000);

    return errorInitialProps;
  }

  // If this point is reached, getInitialProps was called without any
  // information about what the error might be. This is unexpected and may
  // indicate a bug introduced in Next.js, so record it in Sentry
  Sentry.captureException(new Error(`_error.js getInitialProps missing data at path: ${asPath}`));
  await Sentry.flush(2000);

  return errorInitialProps;
};

export default MyError;

Expected Result

Works with typescript

Actual Result

Typescript switch is not present in the docs page.

Current errors are

Argument of type '{ res: ServerResponse; err: Error & { statusCode?: number; }; }' is not assignable to parameter of type 'NextPageContext'.
  Type '{ res: ServerResponse; err: Error & { statusCode?: number; }; }' is missing the following properties from type 'NextPageContext': pathname, query, AppTreets(2345)
Property 'hasGetInitialPropsRun' does not exist on type 'ErrorProps'.ts(2339)
lforst commented 2 years ago

Hi @iamchathu, thanks for reporting this. It is something we should fix.

(Or at the very least mention in our docs if it is an issue with next.js types)

lforst commented 2 years ago

I looked a bit more into it.

To fix the first error: You need to pass the entire context parameter that is passed to MyError.getInitialProps into NextErrorComponent.getInitialProps. That's not a bug on the SDK side but a type requirement by next.js.

MyError.getInitialProps = async (context) => {
  const errorInitialProps = await NextErrorComponent.getInitialProps(context);

The second error you can fix by providing a proper type for errorInitialProps:

const errorInitialProps: ErrorProps & { hasGetInitialPropsRun?: boolean } =
    await NextErrorComponent.getInitialProps(context);

Can you try this out and report back whether it worked? Thanks!

iamchathu commented 2 years ago

@lforst

After updating the code to below snippet

import * as Sentry from '@sentry/nextjs';
import NextErrorComponent, { ErrorProps } from 'next/error';

const MyError = ({ statusCode, hasGetInitialPropsRun, err }) => {
  if (!hasGetInitialPropsRun && err) {
    // getInitialProps is not called in case of
    // https://github.com/vercel/next.js/issues/8592. As a workaround, we pass
    // err via _app.js so it can be captured
    Sentry.captureException(err);
    // Flushing is not required in this case as it only happens on the client
  }

  return <NextErrorComponent statusCode={statusCode} />;
};

MyError.getInitialProps = async (context) => {
  const errorInitialProps: ErrorProps & { hasGetInitialPropsRun?: boolean } = await NextErrorComponent.getInitialProps(
    context,
  );

  // Workaround for https://github.com/vercel/next.js/issues/8592, mark when
  // getInitialProps has run

  errorInitialProps.hasGetInitialPropsRun = true;

  // Running on the server, the response object (`res`) is available.
  //
  // Next.js will pass an err on the server if a page's data fetching methods
  // threw or returned a Promise that rejected
  //
  // Running on the client (browser), Next.js will provide an err if:
  //
  //  - a page's `getInitialProps` threw or returned a Promise that rejected
  //  - an exception was thrown somewhere in the React lifecycle (render,
  //    componentDidMount, etc) that was caught by Next.js's React Error
  //    Boundary. Read more about what types of exceptions are caught by Error
  //    Boundaries: https://reactjs.org/docs/error-boundaries.html

  if (context.err) {
    Sentry.captureException(context.err);

    // Flushing before returning is necessary if deploying to Vercel, see
    // https://vercel.com/docs/platform/limits#streaming-responses
    await Sentry.flush(2000);

    return errorInitialProps;
  }

  // If this point is reached, getInitialProps was called without any
  // information about what the error might be. This is unexpected and may
  // indicate a bug introduced in Next.js, so record it in Sentry
  Sentry.captureException(new Error(`_error.js getInitialProps missing data at path: ${context.asPath}`));
  await Sentry.flush(2000);

  return errorInitialProps;
};

export default MyError;

Gets a warning

warn  - You have added a custom /_error page without a custom /404 page. This prevents the 404 page from being auto statically optimized.
See here for info: https://nextjs.org/docs/messages/custom-error-no-custom-404
lforst commented 2 years ago

Gets a warning

warn  - You have added a custom /_error page without a custom /404 page. This prevents the 404 page from being auto statically optimized.
See here for info: https://nextjs.org/docs/messages/custom-error-no-custom-404

That warning is a message from next.js itself. Follow the link and it tells you to add a /404 page, which I recommend you do if you want your 404 page to be statically optimized.

I will close the issue because your problem with the sentry SDK seems to be resolved. Feel free to open another issue if you encounter further problems!

Btw thanks for calling us out on the types 😁 - we will adjust our docs for a more seamless setup when using TypeScript + Next!

iamchathu commented 2 years ago

@lforst Better to add documentation about 404 warning too. Since sentry documentation are clear and covers all cases. Thanks