はじめに
私たちが運用する SaaS は、Next.js による Web フロント 5 アプリ・Expo の Mobile・Electron の Desktop・NestJS の API・Cloud Run Job の Worker、合計 9 個のアプリ を 1 つのモノレポで管理しています。
エラー監視として Sentry を採用することにし、公式ドキュメントが推奨する Wizard (npx @sentry/wizard@latest -i <platform>) で全部済むと思っていました。
実際は、半分は手動で仕上げる必要があり、想定外の罠も複数遭遇 しました。本記事では、その経験を共有します。
Wizard 対応と非対応の振り分け
Sentry Wizard の -i フラグで指定できる platform を確認すると、対応・非対応がはっきり分かれます。
| アプリ | フレームワーク | Wizard | SDK |
|---|---|---|---|
| Web フロント 5 個 | Next.js | ✅ -i nextjs | @sentry/nextjs |
| Desktop | Electron | ✅ -i electron | @sentry/electron |
| Mobile | Expo / React Native | ✅ -i reactNative | @sentry/react-native |
| API | NestJS | ❌ | @sentry/nestjs (手動) |
| Worker | Node.js (Cloud Run Job) | ❌ | @sentry/node (手動) |
NestJS と汎用 Node.js は Wizard 非対応のため、Dashboard で Project を手動作成 → SDK install → init コード追加 → DSN 投入を自前で行うことになります。
罠 1: Wizard 半自動の Electron / React Native
Electron と React Native の Wizard は、Next.js のように「config 全部生成 + 質問に答えるだけ」とはいきません。
Electron の場合
npx @sentry/wizard@latest -i electron
を実行すると以下のメッセージで止まります:
✗ @sentry/electron isn't in your dependencies
please install it with yarn/npm
事前 install が前提 です。先に npm install @sentry/electron してから再実行する必要があります。
さらに、Wizard は sentry.properties をコメントアウトされた雛形しか生成しません:
defaults.url=https://sentry.io/
#defaults.org=
#defaults.project=
#auth.token=
cli.executable=../../node_modules/@sentry/cli/bin/sentry-cli
org / project / auth.token を手動で書く必要があります。さらに main process と renderer process それぞれに Sentry.init コードを書く必要があり、自動生成はされません。
React Native の場合
npx expo install @sentry/react-native
npx @sentry/wizard@latest -i reactNative
Wizard が app.json への plugin 追加スニペットを表示しますが、私たちは app.config.ts (TypeScript dynamic config) を使っていたため、スニペットを TypeScript シンタックスに変換し、既存の plugins 配列にマージする手作業が必要でした。
plugins: [
// ... 既存
[
"@sentry/react-native/expo",
{ url: "https://sentry.io/", project: "...", organization: "..." },
],
],
罠 2: Wizard が CommonJS で書く → ES module 化が必要
Next.js Wizard が完了後、dev サーバを起動するとビルドエラー:
ReferenceError: require is not defined in ES module scope
This file is being treated as an ES module because it has a '.js' file extension and
'.../package.json' contains "type": "module".
Wizard が next.config.js の末尾に追加するコードが CommonJS 形式 だったためです:
// Wizard が追加 (CommonJS)
const { withSentryConfig } = require("@sentry/nextjs");
module.exports = withSentryConfig(module.exports, {
/* ... */
});
私たちの Next.js アプリは "type": "module" で ES module を採用していたため、5 アプリすべてを以下のように書き換えました:
// 冒頭
import { withSentryConfig } from "@sentry/nextjs";
// 末尾
export default withSentryConfig(nextConfig, {
org: "...",
project: "...",
silent: !process.env.CI,
tunnelRoute: "/monitoring",
});
罠 3: Docker volume の anonymous volume
私たちの Docker Compose は anonymous volume を使って image の node_modules を保持しています:
volumes:
- ./services/turborepo:/app
- /app/node_modules # anonymous volume
これにより、ホスト側で npm install @sentry/nestjs --workspace=apps/api を実行しても、コンテナ内の /app/node_modules には反映されません。
Cannot find module '@sentry/nestjs' or its corresponding type declarations.
docker compose exec api npm install も node-gyp 系の native module rebuild で失敗します。最終的に image rebuild が必要でした(以下は共通イメージを使うワーカー系サービスを worker で代表させた例です):
docker compose build worker # 共通 turborepo image を rebuild
docker compose stop api worker
docker compose rm -f api worker # anonymous volume を削除
docker compose up -d api worker # 新 image で起動
罠 4: アプリごとに異なる env vars 規約
Sentry DSN を投入する env vars 名が、フレームワークごとに 4 種類に分かれます:
| アプリ | env var prefix | 例 |
|---|---|---|
| Next.js | NEXT_PUBLIC_* (build 時置換) | NEXT_PUBLIC_SENTRY_DSN |
| Electron + Vite | import.meta.env.MAIN_VITE_* | MAIN_VITE_SENTRY_DSN |
| Expo | EXPO_PUBLIC_* (Metro 置換) | EXPO_PUBLIC_SENTRY_DSN |
| NestJS / Node.js | process.env.* (runtime 評価) | SENTRY_DSN |
特に Electron + Vite は process.env.SENTRY_DSN ではなく import.meta.env.MAIN_VITE_SENTRY_DSN を読む必要があり、最初に process.env で書いたら値が undefined になり Sentry が起動しないという、見落としやすい罠でした。
apps/desktop/src/main/env.d.ts への型定義追加も忘れずに:
interface ImportMetaEnv {
readonly MAIN_VITE_SENTRY_DSN?: string;
}
罠 5: Session Replay の PII 露出
Wizard デフォルトの Session Replay 設定は 入力フォームの値はマスクされるが、表示済テキストはマスクされない ため、給与額・取引金額・氏名等の機密情報がそのまま動画記録されるリスクがあります。
業務 SaaS では致命的なため、すべてマスクするように厳格化:
Sentry.replayIntegration({
maskAllText: true,
maskAllInputs: true,
blockAllMedia: true,
}),
これで UI 構造のみ録画され、PII 漏洩リスクはゼロになります。デバッグ価値は下がりますが、業務 SaaS では妥当なトレードオフです。
設計判断: env-tag 方式 vs Project 分離方式
DEV / STG / PRD の環境を Sentry でどう分けるか、2 つの選択肢がありました:
| 観点 | env-tag 方式 (採用) | env 別 Project 方式 (不採用) |
|---|---|---|
| Project 数 | 9 | 27 (9 × 3 env) |
| Issue 比較 | 同 project 内で「DEV で fix → PRD で再発」が即わかる | env 跨ぎ比較不可 |
| Release tracking | env 横断で release 進捗追跡可 | env 別に分断 |
| Source map upload | project 単位で 1 回 | env 別に重複 |
| Spike Protection | env 別ではない (デメリット) | env 別に分離可 |
私たちの規模 (β 期間) ではメリットが大きく、env-tag 方式 (1 project = 1 DSN、env 切替は SENTRY_ENVIRONMENT tag のみ) を採用しました。dev での event が過剰に発生するリスクは、Inbound filter で environment=development を破棄することで対応しています。
設計判断: Logs 機能を採用しない (ログ責務分離)
Sentry の Logs 機能 (2025 GA) は Wizard で Yes (recommended) がデフォルトですが、No を選択 しました。
私たちはログを 4 系統に責務分離しています:
| ログ種別 | 保管先 |
|---|---|
| インフラ層 (Cloud Run stdout / SQL slow query / GCS access) | Cloud Logging |
| アプリ層エラー (exception) | Sentry |
| エラー周辺コンテキストログ | Sentry breadcrumbs (default で console.log 自動 capture) |
顧客側監査ログ (audit_log) | BigQuery (CDC 経由) |
Sentry breadcrumbs で error 直前 100 件の console.log / fetch / click が自動 capture されるため、Logs 機能を別途有効化しなくてもデバッグに必要なコンテキストは取得済。二重保管 + 運用複雑性を回避できます。
まとめ
| 項目 | 結論 |
|---|---|
| 9 アプリ統合の所要時間 | 約 4 時間 (Wizard 自動部分は 5 分 × 7 アプリ、手動仕上げと罠対応で 3 時間半) |
| Wizard だけで完結 | しない (NestJS / Node.js は完全手動、Electron / RN は半自動) |
| 想定外の罠 | 5 つ (CommonJS / docker volume / env vars 規約 / PII / 半自動 Wizard 等) |
| 動作確認 | 全 9 project で test event 着弾確認 (eventId 発行 + flush success) |
公式ドキュメントの Wizard 推奨は決して間違っていませんが、モノレポ × 多フレームワーク × Docker × 厳格 PII という条件が重なると、半分は手動仕上げになります。本記事が同じ構成のチームの参考になれば幸いです。