👁️

Next.js のアプリケーションに Sentry を入れるときに environment を環境変数で渡す

2024/04/07に公開

Sentry とは

ユーザーがアプリケーションを操作した内容を記録して、デバッグしやすくしてくれるツールです。Datadog RUMに似ています。ユーザーが操作中にUnhandled Errorが発生した場合などに、エラーを送信してくれます。

Next.js で作られたアプリケーションに Sentry を入れよう

こちらの手順に従って、 npx @sentry/wizard@latest -i nextjs するだけでよろしい。あとは、画面に沿って設定していくだけです。

その結果、以下のファイルが書き換えられたり作成されたりします。

  • next.config.mjs
    • Sentryを使うための設定が自動で記述されます
  • sentry.client.config.ts
    • クライアントサイドに挿入されるスクリプトです。Sentryの初期化を行なっています。ユーザーによるブラウザー上での操作を記録するために必要です。
  • sentry.server.config.ts
    • サーバーサイドに挿入されるスクリプトです。こっちは今回はあまり関係ない。

environment を設定しよう

Sentry にはプロジェクトという概念でアプリケーションごとにわけることができます。では、アプリケーションが staging なのか production なのか知りたいときにはどうすればよいのでしょうか。

Sentry の初期化時に environment を設定することで環境をわけて登録できます。以下のようにすれば、 staging という環境のログを記録することができます。

sentry.client.config.ts
Sentry.init({
  // 割愛
  environment: "staging",
  // 割愛
});

アプリケーションをプロダクションビルドした上で environment に環境変数の値をセットしたい

以下のようなことができるといいですね。

sentry.client.config.ts
Sentry.init({
  environment: process.env.SENTRY_ENV,
});

だが Next.js のプロダクションビルドには罠がある

Next.js のアプリケーションを Docker ビルドしている人は少ないんでしょうか。私は Docker で動かしたいので、 Next.js のプロダクションビルドはとっても困るんですよね。ブラウザーで動くコードに書かれている環境変数は、プロダクションビルド時に環境変数に設定されている内容で固定されてしまうという仕様があるため、ビルド後に環境変数を差し替えることができません。

つまり、普通に考えると、 staging と production という 2 つの環境があるとき、それぞれの Docker イメージをビルドしてやる必要がありそうです。ですが、この方法は 2 倍容量を食うというデメリット以外にも staging にデプロイしたバイナリと production にデプロイするバイナリが異なるため、真に動作確認をしたとは言えないという大きなデメリットがあると思います。なので、これはやりたくない。

そこで、次の方法で、 environment を環境変数から渡せるようにしました。

環境変数の内容をブラウザー側で読み取れるようにする

まずは Sentry 関係なしに、汎用的な話をします。動的に変化する環境変数の内容をブラウザー側で読み取れるようにする方法についてです。

これはNext.jsのドキュメントに書かれている方法なのですが、 getServerSidePropsAppRouter を使って、 SSR のタイミングで環境変数を読み出して、 CSR コンポーネント( "use client" してるコンポーネント)に渡すようにするとうまくいきます。つまり以下のようなコードになります。

server.tsx
const SSRComponent = () => {
  const environment = process.env.SOME_ENV;
  return (
    <CSRComponent environment={environment} />
  );
}
client.tsx
"use client";
const CSRComponent = ({ environment }) => {
  return (
    <h1>{environment}</h1>
  );
}

SSR 時には動的に環境変数にアクセスできるので、 SSR コンポーネントが CSR コンポーネントに環境変数を渡してやればよいということです。

Sentry の初期化時に環境変数を渡したい

以下の手順で Sentry を初期化するコードを書きます。AppRouterを使う方法のみ紹介します。

  • sentry.client.config.ts の中身をカラッポにする
    • 初期化コードは CSR コンポーネントに書くのでここでは初期化する必要ありません。
    • ファイルを消すと Next.js のビルドでエラーになるので、消してはいけない。
  • SSRComponent を作る
    • sentry.ssr.tsx
      const SSRComponent = () => {
          const env = process.env.SENTRY_ENV;
          return <CSRComponent environment={env} />;
      }
      
  • CSRComponent を作る
    • sentry.csr.tsx
      "use client";
      const CSRComponent = ({ environment }) => {
        useEffect(() => {
          Sentry.init({
            // 割愛
            environment: environment,
            // 割愛
          })
        }, [environment]);
        return null;
      }
      
  • layout.tsx で SSRComponent を Render する。
    • layout.tsx
      ...
      <html>
        ...
        <SSRComponent />
        ...
      </html>
      ...
      

これで以上になります。
問題点があるとすると、 Sentry.init が行われるより前に発生したエラーはキャプチャできないことくらいですが、 layout.tsx より前に行われる処理はそれほど多くないはずなので、まあよいだろうと判断しています。

他の方法として、 instrumentation.ts というファイルを作成すると色々できるっぽいので、そっちでやるのもよいのかもしれませんが、試していません。

まとめ

  • Sentry が最初に生成したコードを捨てて、自前で実装する(しかないの?ほんとに?)

以上です。よろしくお願いいたします。

Discussion