Next.js のアプリケーションに Sentry を入れるときに environment を環境変数で渡す
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.init({
// 割愛
environment: "staging",
// 割愛
});
アプリケーションをプロダクションビルドした上で environment に環境変数の値をセットしたい
以下のようなことができるといいですね。
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のドキュメントに書かれている方法なのですが、 getServerSideProps
や AppRouter
を使って、 SSR のタイミングで環境変数を読み出して、 CSR コンポーネント( "use client"
してるコンポーネント)に渡すようにするとうまくいきます。つまり以下のようなコードになります。
const SSRComponent = () => {
const environment = process.env.SOME_ENV;
return (
<CSRComponent environment={environment} />
);
}
"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