Next.js+Vercel 및 Sentry 결합

개요


Next.다음은 js+Vercel에서 사용되는 응용 프로그램을 Sentry와 결합시키는 절차입니다.
이번에는 자신이 실제 개발·운용한 서비스친구/친구에 이러한 설정을 추가해 실제 설정 내용에 따라 설명한다.
LGTMeow

독자 대상


Next.js의 응용 프로그램을 Vercel로 설계한 사람을 대상 독자로 삼다.

사전 준비


Sentry에 서명


다음 페이지에서 서명을 시작합니다.
https://lgtmeow.com
GiitHub 계정을 이용해서 만들었어요.

Sentryorganization의 제작


다음 페이지에서 작성합니다.
https://sentry.io/signup/
GiitHub Organization과 같은 이름으로 만들었어요.

Sentry 프로젝트 제작


프로젝트를 작성합니다.
create-project
  • 플랫폼을 선택합니다.js 선택
  • Set your default alert settings 우선 선택I'll create my own alerts later
  • 프로젝트 이름은 상관없지만 제 이름은 GiitHub 창고와 같습니다
  • create-project2

    응용 프로그램에서 Sentry 설정


    왼쪽'프로젝트'에서 제작된 프로젝트를 확인할 수 있을 것 같습니다.
    create-project3
    설치 방법을 누르면 응용 프로그램 설정 화면이 나타나므로 Next.js 를 선택합니다.
    setup1
    Next.js를 선택하면 다음과 같은 설치 방법을 보여 줍니다.
    configure-next-js
    여기에 기재된 내용에 따라 패키지 설치와 설정 파일 수정 등을 진행한다.

    @sentry/nextjs 설치 및 설정


    다음을 수행합니다.
    npm install --save @sentry/nextjs
    

    Sentrywizard의 실행


    다음을 수행합니다.
    npx @sentry/wizard -i nextjs
    
    브라우저에서 다음 페이지가 열립니다.
    Sentry의 인증 엔드포인트로 마이그레이션하고 토큰을 가져오는 중일 수 있습니다.(확인되지 않음)
    browser
    터미널에서 사용할 항목을 선택하는 화면입니다. 선택하십시오.
    SentryWizard1
    항목을 선택하면 Sentrywizard가 완료됩니다.
    SentryWizard2
    다양한 파일을 생성, 수정합니다.

    Sentrywizard에서 추가된 파일


    sentry.client.config.js
    // This file configures the initialization of Sentry on the browser.
    // The config you add here will be used whenever a page is visited.
    // https://docs.sentry.io/platforms/javascript/guides/nextjs/
    
    import * as Sentry from '@sentry/nextjs';
    
    const SENTRY_DSN = process.env.SENTRY_DSN || process.env.NEXT_PUBLIC_SENTRY_DSN;
    
    Sentry.init({
      dsn: SENTRY_DSN || 'https://[email protected]/0000000',
      // Adjust this value in production, or use tracesSampler for greater control
      tracesSampleRate: 1.0,
      // ...
      // Note: if you want to override the automatic release value, do not set a
      // `release` value here - use the environment variable `SENTRY_RELEASE`, so
      // that it will also get attached to your source maps
    });
    
    sentry.server.config.js
    // This file configures the initialization of Sentry on the server.
    // The config you add here will be used whenever the server handles a request.
    // https://docs.sentry.io/platforms/javascript/guides/nextjs/
    
    import * as Sentry from '@sentry/nextjs';
    
    const SENTRY_DSN = process.env.SENTRY_DSN || process.env.NEXT_PUBLIC_SENTRY_DSN;
    
    Sentry.init({
      dsn: SENTRY_DSN || 'https://[email protected]/0000000',
      // Adjust this value in production, or use tracesSampler for greater control
      tracesSampleRate: 1.0,
      // ...
      // Note: if you want to override the automatic release value, do not set a
      // `release` value here - use the environment variable `SENTRY_RELEASE`, so
      // that it will also get attached to your source maps
    });
    
    sentry.properties
    defaults.url=https://sentry.io/
    defaults.org=あなたのSentry organization名
    defaults.project=あなたのSentryプロジェクト名
    cli.executable=../../.npm/_npx/xxxxxxxxxxxxxxxx/node_modules/@sentry/cli/bin/sentry-cli
    
    next.config.js
    // This file sets a custom webpack configuration to use your Next.js app
    // with Sentry.
    // https://nextjs.org/docs/api-reference/next.config.js/introduction
    // https://docs.sentry.io/platforms/javascript/guides/nextjs/
    
    const { withSentryConfig } = require('@sentry/nextjs');
    
    const moduleExports = {
      // Your existing module.exports
    };
    
    const sentryWebpackPluginOptions = {
      // Additional config options for the Sentry Webpack plugin. Keep in mind that
      // the following options are set automatically, and overriding them is not
      // recommended:
      //   release, url, org, project, authToken, configFile, stripPrefix,
      //   urlPrefix, include, ignore
    
      silent: true, // Suppresses all logs
      // For all available options, see:
      // https://github.com/getsentry/sentry-webpack-plugin#options.
    };
    
    // Make sure adding Sentry options is the last code to run before exporting, to
    // ensure that your source maps include changes from all other Webpack plugins
    module.exports = withSentryConfig(moduleExports, sentryWebpackPluginOptions);
    
    src/pages/_error.js
    import NextErrorComponent from 'next/error';
    
    import * as Sentry from '@sentry/nextjs';
    
    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 = await NextErrorComponent.getInitialProps(context);
      
      const { res, err, asPath } = context;
    
      // Workaround for https://github.com/vercel/next.js/issues/8592, mark when
      // getInitialProps has run
      errorInitialProps.hasGetInitialPropsRun = true;
    
      // Returning early because we don't want to log 404 errors to Sentry.
      if (res?.statusCode === 404) {
        return errorInitialProps;
      }
      
      // 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;
    
    .sentryclirc
    [auth]
    token=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
    

    next.config.정보


    프로젝트 경로가 next.config.js 이미 존재하면 next.config.wizardcopy.js 의 이름으로 생성됩니다.
    대다수 항목next.config.js이 이미 존재하기 때문에 대다수 상황에서 생성된다next.config.wizardcopy.js.
    이 경우 기존next.config.js과 합병해야 한다.
    필자의 환경next.config.js은 다음과 같다.
    next.config.js
    const { withSentryConfig } = require('@sentry/nextjs');
    
    /**
     * @type {import('next').NextConfig}
     */
    const moduleExports = {
      images: {
        domains: ['lgtm-images.lgtmeow.com', 'stg-lgtm-images.lgtmeow.com'],
      },
      swcMinify: true,
    };
    
    const sentryWebpackPluginOptions = {
      silent: true,
    };
    
    module.exports = withSentryConfig(moduleExports, sentryWebpackPluginOptions);
    
    필자가 확인하지 않았을 때withSentryConfig 이외에 여러 플러그인을 사용할 때https://sentry.io/organizations/new/가 비교적 좋다.
    미리 참고 가치가 있는 보도를 붙이다.
    (참고 자료)next-compose-plugins

    src/pages/_error.정보


    필자의 환경에서 만들었다src/pages/_error.tsx.
    따라서 생성된src/pages/_error.js 내용을 병합하면서 TypeScript의 대응을 할 필요가 있다.
    AS를 사용하여 강제로 해결하는 형식이 있는 부분도 있지만, 잠시 이 동작을 사용했습니다.
    src/pages/_error.tsx
    import * as Sentry from '@sentry/nextjs';
    import { NextPage, NextPageContext } from 'next';
    import NextErrorComponent from 'next/error';
    
    import { httpStatusCode, HttpStatusCode } from '../constants/httpStatusCode';
    
    type Props = {
      statusCode: HttpStatusCode;
      err?: Error;
      hasGetInitialPropsRun?: boolean;
    };
    
    const CustomErrorPage: NextPage<Props> = ({
      statusCode,
      hasGetInitialPropsRun,
      err,
    }) => {
      if (!hasGetInitialPropsRun && err) {
        Sentry.captureException(err);
      }
    
      return <NextErrorComponent statusCode={statusCode} />;
    };
    
    const defaultTimeout = 2000;
    
    CustomErrorPage.getInitialProps = async (
      context: NextPageContext,
    ): Promise<Props> => {
      const errorInitialProps = (await NextErrorComponent.getInitialProps(
        context,
      )) as Props;
    
      const { res, err, asPath } = context;
    
      errorInitialProps.hasGetInitialPropsRun = true;
    
      if (res?.statusCode === httpStatusCode.notFound) {
        return errorInitialProps;
      }
    
      if (err) {
        Sentry.captureException(err);
    
        await Sentry.flush(defaultTimeout);
    
        return errorInitialProps;
      }
    
      Sentry.captureException(
        new Error(`_error.tsx getInitialProps missing data at path: ${asPath}`),
      );
      await Sentry.flush(defaultTimeout);
    
      return errorInitialProps;
    };
    
    export default CustomErrorPage;
    
    뒷말import { httpStatusCode, HttpStatusCode } from '../constants/httpStatusCode'; 부분은 HTTP 상태 코드를 나타내는 내용이다.
    src/constants/httpStatusCode.ts
    // https://developer.mozilla.org/ja/docs/Web/HTTP/Status から必要なものを抜粋して定義
    export const httpStatusCode = {
      ok: 200,
      created: 201,
      accepted: 202,
      noContent: 204,
      movedPermanently: 301,
      badRequest: 400,
      unauthorized: 401,
      forbidden: 403,
      notFound: 404,
      requestTimeout: 408,
      unprocessableEntity: 422,
      internalServerError: 500,
      serviceUnavailable: 503,
    } as const;
    
    export type HttpStatusCode = typeof httpStatusCode[keyof typeof httpStatusCode];
    

    Sentrywizard에서 업데이트된 파일

    .gitignore에는 다음과 같은 내용이 추가된다..sentryclirc에 기밀정보 영패가 기재돼 있어 제출할 수 없음을 고려했다.
    .gitignore
    # Sentry
    .sentryclirc
    

    Next.js API Route 정보


    Next.js의 API Route를 사용하는 경우 매개변수withSentry에 함수를 전달하도록 수정됩니다.
    이렇게 하면 API Route 내에서 발생한 Error도 Sentry에 의해 공지됩니다.
    import type { NextApiRequest, NextApiResponse } from "next"
    import { withSentry } from "@sentry/nextjs";
    
    const handler = async (req: NextApiRequest, res: NextApiResponse) => {
      res.status(200).json({ name: "John Doe" });
    };
    
    export default withSentry(handler);
    

    Sentry에 오류가 발생했는지 로컬에서 확인


    적당한 단추를 눌렀을 때 예외가 발생하도록 프로그램 서버를 시작합니다.
    왼쪽 메뉴의 과제를 확인하면 오류 발송을 확인할 수 있다.
    issues
    자세한 내용을 확인하면 오류 발생 시 브라우저 정보와 IP 주소 등을 확인할 수 있습니다.
    ErrorDetail1
    ErrorDetail2

    Vercel 및 Sentry 제휴


    Sentry Integration을 사용하는 것이 가장 간단하므로 먼저 단계를 구현합니다.
    Next.Vercel의 합작을 가볍게 하는 일
  • Visit https://docs.sentry.io/product/integrations/deployment/vercel/
  • 여기를 따라https://vercel.com/integrations/sentry/add로 이동하고 "Add Integration"을 누릅니다.
    AddIntegration1
    추가할 Vercel의 Teams를 선택합니다.
    AddIntegration2
    여기서 필요한 Vercel 항목만 선택합니다.
    AddIntegration3
    Install Vercel 키를 누릅니다.
    AddIntegration4
    Vercel 항목과 Sentry 항목을 연합하여 "Compulete on Vercel"을 누릅니다.
    AddIntegration5
    이렇게 하면 완성된다.Vercel에서 환경 변수를 확인한 후 Sentry에서 추가한 환경 변수를 확인할 수 있습니다.
    필요한 환경 변수는 문제없습니다. handler 에서 읽을 수 있는 설정입니다.
    필요에 따라 설정을 변경하여 Production,Preview도 읽을 수 있도록 한다.
    Vercel

    Vercel에서 작업 확인


    Sentry Integration은 Sentry 작업에 필요한 환경 변수를 추가하기 때문에 각 설정 파일도 환경 변수에서 필요한 값을 로드하는 것으로 변경됩니다.

    Development의 수정 사항

    next.config.js,authToken,org는 환경 변수에서 불러오는 것으로 변경되었다.
    next.config.js
    const { withSentryConfig } = require('@sentry/nextjs');
    
    /**
     * @type {import('next').NextConfig}
     */
    const moduleExports = {
      // 任意の設定
    };
    
    const sentryWebpackPluginOptions = {
      silent: true,
      authToken: process.env.SENTRY_AUTH_TOKEN,
      org: process.env.SENTRY_ORG,
      project: process.env.SENTRY_PROJECT,
    };
    
    module.exports = withSentryConfig(moduleExports, sentryWebpackPluginOptions);
    

    project , sentry.client.config.js


    내용이 모두 같다.
    Vercel에 등록된 sentry.server.config.js를 참조합니다.
    그런 다음 설정dsn합니다.
    이걸 설정하면 어느 환경에서 일어났는지 Sentry에서 쉽게 알 수 있으니 추천해드려요.
    필자는 환경 변수NEXT_PUBLIC_SENTRY_DSN를 다음과 같이 정의했다.
  • Vercel에서 Development인 경우environment를 지정하여 로컬 환경에서 발생하는 Error
  • 로 식별하기
  • Vercel에 Preview를 지정하는 경우NEXT_PUBLIC_APP_ENV Vercel의 개발 환경에서 발생한 Error
  • 로 식별하기
  • Vercel에서 Production의 경우 지정local, Vercel의 공식 환경에서 발생한 Error
  • 로 식별하기 위해
    ↓ 이렇게 필터링이 가능하고 공지할 때 공식 환경에서 일어나는 것만 공지하는 등 대응이 가능해져 편리하다.
    Filter
    sentry.client.config.js
    import * as Sentry from '@sentry/nextjs';
    
    Sentry.init({
      dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
      environment: process.env.NEXT_PUBLIC_APP_ENV,
      tracesSampleRate: 1.0,
    });
    
    sentry.server.config.js
    import * as Sentry from '@sentry/nextjs';
    
    Sentry.init({
      dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
      environment: process.env.NEXT_PUBLIC_APP_ENV,
      tracesSampleRate: 1.0,
    });
    

    개발자 삭제

    production,sentry.properties,authToken는 환경 변수에서 불러왔기 때문에 이 파일은 삭제되었고 문제가 없습니다.

    Vercel에서 디버깅 후 동작 확인


    다음과 같이 Vercel에서 발생한 Error에게도 공지가 있는지 확인할 수 있습니다.
    VercelError

    끝말


    다음은 org를 사용하는 Sentry 설정에 대한 설명입니다.
    환경 변수의 추가를 잊고 움직이지 않거나 푹 빠진 부분도 있기 때문에 정리해봤습니다.
    이 기사는 입문을 위한 것이지만 좀 더 실천적인 내용https://vercel.com/integrations/sentry/add을 설명해 준 사람이 있기 때문에 참고 링크로 게재됐다.
    이 기사에서 Sentry를 가져온 후 읽는 것을 추천합니다.
    Next.js에서 Sentry를 가져올 때의 과제와 해결 방법
    이상입니다.끝까지 읽어주셔서 감사합니다.

    좋은 웹페이지 즐겨찾기