言語を切り替える
テーマを切り替える

Next.js 本番環境モニタリング完全ガイド:Sentry 連携・ログ管理・アラート設定の実践

金曜日の夜 9 時 17 分、スマートフォンが震えました。

チャットグループは大騒ぎです。「決済ページが開かない」「さっき注文が失敗した」「画面が真っ白だ」。ローカルで動かしてみれば、完璧に動きます。サーバーログを確認しても、「Internal Server Error」の一行だけ。ユーザーは決済ボタンを押したら画面が固まったと言うのに、再現できません。

その週末、Vercel のデプロイログを追い、テスト版を再デプロイし続け、最終的に本番環境でのサードパーティ決済 SDK の偶発的タイムアウトが原因だと判明しました。原因特定までのプロセスは、暗闇の中で落とした針を探すようなものでした。

Next.js アプリは開発環境ではスムーズに動いても、本番に出すとパンドラの箱が開く——SSR が散発的に 500 になる、エッジ関数が不可解なエラーを出す、API の応答時間が急上昇するのにボトルネックが分からない。

原因はシンプルです。本番環境向けの包括的なモニタリング体制がないこと。本記事では、Sentry によるエラー追跡から構造化ログ管理、パフォーマンス監視、アラート設定まで、Next.js モニタリングの構築手順を解説します。理論の羅列ではなく、コピー&ペーストで使える設定コードが中心です。読み終えれば、次に本番で問題が起きたとき、ユーザーより先に気づけるはずです。

なぜ Next.js には専用のモニタリングが必要か

Next.js の「三頭怪」特性

従来のフロントエンドアプリはブラウザだけで動き、エラーは DevTools のコンソールで確認できます。Next.js は違います——同一アプリが 3 つのまったく異なる場所で動きます

  • クライアント(Browser):ユーザーのブラウザ上の React コンポーネント
  • サーバー(Node.js):SSR レンダリング、API Routes、Server Actions
  • エッジ(Edge Runtime):ミドルウェア、エッジ関数

決済機能だけでも、クライアントのフォーム検証 → ミドルウェアでの認証 → Server Action 呼び出し → API Route で DB 参照 → クライアントへ結果表示、と複数段階を経ます。どこか 1 箇所でエラーが起きても、従来のブラウザ監視では全体像が見えません

昨年、奇妙なバグに遭遇しました。ユーザーから「ページの読み込みが遅く、500 が表示される」と報告が来たのです。ブラウザの Network パネルではリクエストが遅いことは分かるが、どこが遅いのか不明。Sentry の分散トレーシングを導入して初めて、SSR 中に呼んでいたサードパーティ API の応答時間が通常 200ms から 8 秒に急増していたことが判明しました。クライアント監視では永遠に気づけない類の問題です。

SSR の「ブラックボックス」

サーバー側レンダリングでエラーが起きると、ユーザーが見るのは真っ白な 500 ページだけ。スタックトレースもコンテキストも何もありません

さらに厄介なのが Hydration エラーです。こんな警告を見たことがあるでしょう:

Warning: Expected server HTML to contain a matching <div> in <div>

開発環境では目立たないこともありますが、本番ではページ全体のインタラクションが効かなくなることがあります。監視がなければ、ユーザーから「ボタンが押せない」と言われるまで気づけません。

Vercel のデータによると、SSR 関連のエラーは Next.js 本番問題の約 35% を占めます。これはエラーだけで、パフォーマンス問題は含みません——例えば特定コンポーネントの SSR 時間が急に伸び、ユーザーは「ページが遅くなった」と感じるのに、ボトルネックが分からない、といったケースです。

完全なモニタリングの 4 本柱

信頼できる Next.js モニタリングは、次の 4 点をカバーする必要があります:

エラー追跡
例外の捕捉だけでなく、誰が(ユーザー情報)、どんな環境で(デバイス、ブラウザ、ネットワーク)、何をしていたか(ブレッドクラム)、関連リクエストパラメータは何か、まで把握します。

パフォーマンス監視
LCP(Largest Contentful Paint)は 2.5 秒を超えていないか? API の応答時間は遅くなっていないか? どの DB クエリがリクエスト全体を遅延させているか?

ログ管理
構造化ログで、時間・ユーザー・リクエスト ID による高速検索。開発環境では見やすい出力、本番環境ではログプラットフォームへ集約。

アラート設定
エラー率が閾値を超えたら即座にチームへ通知、新種のエラーは初回出現時に Slack へ、パフォーマンス退行は自動アラート。

この 4 つが揃えば、本番障害時も「暗闇を手探り」にはなりません。ここから順に取り組んでいきましょう。

Sentry 連携実践 — インストールから高度な設定まで

5 分でクイック導入

Sentry の Next.js サポートは成熟しており、公式の自動設定ウィザードがあります。本当に 5 分で済みます:

# SDK をインストール
npm install @sentry/nextjs

# 設定ウィザードを実行
npx @sentry/wizard@latest -i nextjs

ウィザードは Sentry プロジェクト DSN や Source Maps のアップロード有無などを質問し、3 つの設定ファイルを自動生成します:

  • sentry.client.config.ts — ブラウザ環境
  • sentry.server.config.ts — Node.js サーバー
  • sentry.edge.config.ts — Edge Runtime

next.config.js も Sentry の webpack プラグイン付きに更新されます。ウィザードを 1 回走らせれば、基本監視は動きます

ただし、これはスタート地点。本番環境ではより細かい設定が必要です。

App Router のエラー捕捉ポイント

App Router を使っている場合、いくつか注意点があります:

グローバルエラーハンドリング

app/global-error.tsx を作成します。App Router の最後の防波堤です:

'use client';

import * as Sentry from '@sentry/nextjs';
import { useEffect } from 'react';

export default function GlobalError({
  error,
  reset,
}: {
  error: Error & { digest?: string };
  reset: () => void;
}) {
  useEffect(() => {
    // Sentry に送信
    Sentry.captureException(error);
  }, [error]);

  return (
    <html>
      <body>
        <div style={{ padding: '2rem', textAlign: 'center' }}>
          <h2>問題が発生しました</h2>
          <p>エラーを記録しました。早急に修正します</p>
          <button onClick={() => reset()}>再試行</button>
        </div>
      </body>
    </html>
  );
}

Server Actions のエラー捕捉

Server Actions は App Router のキラー機能ですが、エラー処理が見落とされがちです:

'use server';

import * as Sentry from '@sentry/nextjs';

export async function createOrder(formData: FormData) {
  return await Sentry.withServerActionInstrumentation(
    'createOrder', // Sentry に表示される action 名
    {
      recordResponse: true, // レスポンスデータを記録
    },
    async () => {
      // ビジネスロジック
      const productId = formData.get('productId');
      const order = await db.order.create({
        data: { productId, userId: getCurrentUserId() },
      });
      return order;
    }
  );
}

このラップにより、Server Action 内のエラーは自動報告され、実行時間も追跡できます。

本番環境向け最適化設定

Sentry 導入直後、最初の請求書に驚くかもしれません。デフォルト設定ではすべてのイベントが送信されます。サンプリング率の調整が必要です:

// sentry.client.config.ts
import * as Sentry from '@sentry/nextjs';

Sentry.init({
  dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,

  // パフォーマンストレースのサンプリング率
  // 開発 100%、本番 10%(トラフィックに応じて調整)
  tracesSampleRate: process.env.NODE_ENV === 'production' ? 0.1 : 1.0,

  // Session Replay サンプリング
  replaysSessionSampleRate: 0.1,      // 通常セッションの 10% を録画
  replaysOnErrorSampleRate: 1.0,      // エラー発生セッションは 100% 録画

  // 環境識別
  environment: process.env.NEXT_PUBLIC_VERCEL_ENV || 'development',

  // 特定エラーを無視
  ignoreErrors: [
    // ブラウザ拡張機能のエラー
    'ResizeObserver loop limit exceeded',
    // サードパーティスクリプトのエラー
    /chrome-extension/,
    /^Non-Error promise rejection/,
  ],
});

tracesSampleRate 設定の目安

  • 日次 UV < 1 万:0.2〜0.5
  • 日次 UV 1〜10 万:0.1〜0.2
  • 日次 UV > 10 万:0.05〜0.1

当プロジェクトは日次 UV 約 3 万で 0.15 に設定。月間 Sentry クォータの約 60% 消費で、十分なサンプルとコストのバランスが取れています。

Source Maps:デバッグ可能に、ソースは漏らさない

本番の JavaScript は通常 minify されており、エラースタックはこんな感じです:

at r.render (app.js:1:23456)

読めません。Source Maps で難読化コードを元のソースにマッピングできますが、直接公開するとソースコードが漏洩します。

Sentry の方式は、Source Maps を Sentry サーバーにアップロードし、ユーザーのブラウザには配布しないこと。Sentry 内部でのみスタック復元に使います。

CI/CD での設定例(GitHub Actions):

# .github/workflows/deploy.yml
- name: Upload Source Maps to Sentry
  env:
    SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
    SENTRY_ORG: your-org
    SENTRY_PROJECT: your-project
  run: npm run build

next.config.js の Sentry プラグインがアップロードを自動処理します。SENTRY_AUTH_TOKEN は GitHub Secrets に追加し、コードベースにはコミットしないでください。

高度な機能:Session Replay と分散トレーシング

Session Replay はお気に入りの機能です——ユーザーの操作を録画のように再生し、現場を再現できます。

あるユーザーから「決済ボタンが押せない」と報告があり、Session Replay を見ると iPad 横画面で仮想キーボードがボタンを隠していたことが分かりました。エラーログだけでは永遠に気づけない類の問題です。

有効化は簡単。クライアント設定に追加するだけ:

import * as Sentry from '@sentry/nextjs';
import { Replay } from '@sentry/nextjs';

Sentry.init({
  integrations: [
    new Replay({
      maskAllText: false,           // すべてのテキストを隠すか
      blockAllMedia: true,          // すべてのメディアをブロックするか
      maskAllInputs: true,          // フォーム入力を隠す(機密情報漏洩防止)
    }),
  ],
  replaysSessionSampleRate: 0.1,
  replaysOnErrorSampleRate: 1.0,
});

分散トレーシングは 1 リクエストのライフサイクル全体を追跡します。ボタンクリック → フロントエンドリクエスト → API Route で DB 参照 → フロントエンド描画、各ステップの所要時間が見えます。

設定も複雑ではありません。フロントとバックで同じ Sentry.init 設定を使えば、Sentry SDK がリクエストヘッダーに sentry-trace を自動付与します。

カスタムコンテキスト:エラーに意味を持たせる

デフォルトでは Sentry は「エラーが発生した」ことしか知りません。カスタムコンテキストでレポートの価値を高められます:

import * as Sentry from '@sentry/nextjs';

// ユーザー情報を設定
Sentry.setUser({
  id: user.id,
  email: user.email,
  username: user.username,
  // パスワードなど機密情報は入れない!
});

// ビジネスコンテキストを追加
Sentry.setContext('purchase', {
  orderId: '12345',
  amount: 99.99,
  paymentMethod: 'credit_card',
});

// タグを追加(フィルタリング用)
Sentry.setTag('feature', 'checkout');
Sentry.setTag('ab_test', 'variant_b');

エラー発生時、どのユーザーがどんなビジネスシーンで触発したか、すぐに分かります。

実例:ある決済エラーの発生頻度が想定以上に高く、Sentry コンテキストを見るとすべて ab_test: variant_b のユーザーでした。A/B テストの新版決済フローにバグがあり、すぐにそのバリアントを停止。より大きな損失を防げました。

ログ管理 — ログを味方につける

console.log では足りない

初期は console.log だらけでした。デバッグ中は気持ちいいですが、本番では手が届きません:

  • フィルタ不可:10 万行のログから特定ユーザーのリクエストを探す? 幸運を祈ります。
  • 集計不可:過去 1 時間に 1 秒超の DB クエリが何件あったか? 統計できません。
  • アラート不可:ログに “Payment failed” が出ても、誰も気づきません。

構造化ログがこれらを解決します。文字列を単に出力するのではなく JSON オブジェクトを出力し、タイムスタンプ、ログレベル、リクエスト ID、ユーザー ID などのメタデータを付与。任意フィールドで検索・集計できます。

Pino vs Winston:どちらを選ぶか

Node.js エコシステムの 2 大ログライブラリ、それぞれに強みがあります:

特性PinoWinston
性能超高速、非同期ログでオーバーヘッドほぼゼロやや遅いが十分実用的
使いやすさ設定がシンプル、すぐ使える機能豊富、プラグインエコシステム充実
拡張性Transport で拡張複数 Transport を内蔵
コミュニティNext.js 公式推奨老舗、ドキュメント充実

推奨

  • 高並行(QPS > 1000):Pino、性能面で優位
  • 複雑なログ処理(多形式・多出力先):Winston
  • 迷ったら:Pino。Next.js 公式ドキュメントも Pino を使用

Pino 実践設定

まずインストール:

npm install pino
npm install pino-pretty --save-dev  # 開発環境の見やすい出力用

グローバル logger を作成:

// lib/logger.ts
import pino from 'pino';

const logger = pino({
  level: process.env.LOG_LEVEL || 'info',

  // ログレベルのフォーマット
  formatters: {
    level: (label) => ({ level: label.toUpperCase() }),
  },

  // 開発環境は pino-pretty で見やすく
  transport: process.env.NODE_ENV === 'development'
    ? {
        target: 'pino-pretty',
        options: {
          colorize: true,
          translateTime: 'HH:MM:ss',
          ignore: 'pid,hostname',
        },
      }
    : undefined,
});

export { logger };

開発環境はカラーで読みやすい形式、本番環境は JSON でログプラットフォームが解析しやすくなります。

API Route での利用

各リクエストに correlationId(関連 ID)を割り当て、そのリクエストに関するすべてのログを串刺しにするのがポイントです:

// app/api/products/route.ts
import { logger } from '@/lib/logger';
import { randomUUID } from 'crypto';

export async function GET(request: Request) {
  // リクエスト固有 ID を生成
  const correlationId = request.headers.get('x-correlation-id') || randomUUID();

  // correlationId を自動付与する子 logger
  const log = logger.child({ correlationId });

  try {
    log.info({ url: request.url }, 'Processing product request');

    const products = await db.product.findMany();

    log.info({ count: products.length }, 'Products fetched successfully');

    return Response.json(products);
  } catch (error) {
    log.error({ error: error.message, stack: error.stack }, 'Failed to fetch products');
    throw error;
  }
}

同じ correlationId を持つログはすべて関連付けられ、障害調査時にこの ID を検索すればリクエスト全体の流れが一目瞭然です。

ログレベルのベストプラクティス

ログが少なすぎても多すぎても意味がありません。私の分類基準:

ERROR — 即座に対処が必要

  • DB 接続失敗
  • 決済 API 呼び出し失敗
  • クリティカルなビジネスロジックの例外
log.error({ error, userId, orderId }, 'Payment processing failed');

WARN — 異常だが回復可能

  • API 呼び出しのリトライ成功
  • フォールバックロジックの発動
  • クォータ上限に接近
log.warn({ retryCount: 3 }, 'External API retry succeeded');

INFO — 重要なビジネスイベント

  • ログイン/ログアウト
  • 注文作成/完了
  • 重要な設定変更
log.info({ userId, ip }, 'User logged in');

DEBUG — 詳細なデバッグ情報

  • 関数の引数と戻り値
  • 中間状態
  • パフォーマンス計測
log.debug({ params }, 'Calling external API');

本番はデフォルト INFO。障害時のみ一時的に DEBUG に上げて調査します。

ログ集約と分析

ローカル開発は pino-pretty で十分。本番はログプラットフォームへの接続が必要です。主な選択肢:

Vercel Logs
Vercel にデプロイしていれば、組み込みログシステムでゼロ設定。ただし保持 7 日、検索機能は限定的。

Datadog
エンタープライズ向け。APM + ログ + モニタリングのオールインワン。設定例:

import { datadogLogs } from '@datadog/browser-logs';

datadogLogs.init({
  clientToken: process.env.NEXT_PUBLIC_DATADOG_CLIENT_TOKEN,
  site: 'datadoghq.com',
  forwardErrorsToLogs: true,
  sampleRate: 100,
});

Logtail/BetterStack
コスパ良好、ログ分析に特化。リアルタイム検索、アラートルール、カスタムダッシュボードに対応。

個人プロジェクトは Logtail(設定が簡単、月 1GB 無料)。チームプロジェクトは Datadog(高いが機能が強力)。

重要ログフィールドの設計

良いログには次のフィールドを含めます:

{
  "timestamp": "2025-12-20T15:00:06.123Z",  // タイムスタンプ
  "level": "INFO",                          // ログレベル
  "correlationId": "abc-123-def",           // リクエスト関連 ID
  "userId": "user_456",                     // ユーザー ID
  "action": "create_order",                 // ビジネスアクション
  "duration": 234,                          // 実行時間(ms)
  "status": "success",                      // ステータス
  "metadata": {                             // 追加メタデータ
    "orderId": "order_789",
    "amount": 99.99
  }
}

こうしたログは「誰が、いつ、何をして、結果はどうだったか」に答えられます。

パフォーマンス監視 — データドリブンな最適化

Core Web Vitals:Google が重視する指標

Google は Core Web Vitals を検索ランキング要因にしています。あなたも注目すべきです:

  • LCP(Largest Contentful Paint):最大コンテンツ描画時間、理想値 < 2.5s
  • FID(First Input Delay) / INP(Interaction to Next Paint):インタラクション応答時間、< 100ms / < 200ms
  • CLS(Cumulative Layout Shift):累積レイアウトシフト、< 0.1

Next.js には Web Vitals レポートが組み込まれており、app/layout.tsx に数行追加するだけ:

'use client';

import { useReportWebVitals } from 'next/web-vitals';

export function WebVitalsReporter() {
  useReportWebVitals((metric) => {
    // Sentry に送信
    if (window.Sentry) {
      window.Sentry.captureMessage(`Web Vital: ${metric.name}`, {
        level: 'info',
        tags: {
          web_vital: metric.name,
        },
        contexts: {
          web_vitals: {
            value: metric.value,
            rating: metric.rating,
          },
        },
      });
    }

    // または分析プラットフォームへ送信
    fetch('/api/analytics/web-vitals', {
      method: 'POST',
      body: JSON.stringify(metric),
    });
  });

  return null;
}

ルートレイアウトで読み込み:

// app/layout.tsx
export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <WebVitalsReporter />
        {children}
      </body>
    </html>
  );
}

API パフォーマンストレース

フロントの性能は半分。バックエンド API が遅ければユーザーは待たされます。Sentry Performance Monitoring で各 API リクエストを追跡できます:

// app/api/products/[id]/route.ts
import * as Sentry from '@sentry/nextjs';

export async function GET(
  request: Request,
  { params }: { params: { id: string } }
) {
  // transaction を作成
  return await Sentry.startSpan(
    {
      op: 'api.request',
      name: 'GET /api/products/[id]',
    },
    async () => {
      // DB クエリを追跡
      const product = await Sentry.startSpan(
        {
          op: 'db.query',
          name: 'Fetch product from database',
        },
        async () => {
          return await db.product.findUnique({
            where: { id: params.id },
            include: { reviews: true },
          });
        }
      );

      if (!product) {
        return Response.json({ error: 'Not found' }, { status: 404 });
      }

      // 外部 API 呼び出しを追跡
      const pricing = await Sentry.startSpan(
        {
          op: 'http.client',
          name: 'Fetch pricing from external API',
        },
        async () => {
          const res = await fetch(`https://pricing-api.com/product/${params.id}`);
          return res.json();
        }
      );

      return Response.json({ ...product, pricing });
    }
  );
}

Sentry ダッシュボードでは:

  • リクエスト全体 450ms
    • DB クエリ 120ms
    • 外部 API 300ms
    • その他 30ms

一目でボトルネックが外部 API と分かります。

スロークエリアラート

DB はパフォーマンスボトルネックの常連です。Prisma ミドルウェアに監視を追加できます:

// lib/prisma.ts
import { PrismaClient } from '@prisma/client';
import { logger } from './logger';

const prisma = new PrismaClient();

// スロークエリを監視
prisma.$use(async (params, next) => {
  const before = Date.now();
  const result = await next(params);
  const after = Date.now();
  const duration = after - before;

  // 1 秒超のクエリは WARN
  if (duration > 1000) {
    logger.warn({
      model: params.model,
      action: params.action,
      duration,
      args: params.args,
    }, 'Slow database query detected');

    // Sentry にも送信
    Sentry.captureMessage('Slow database query', {
      level: 'warning',
      tags: { model: params.model, action: params.action },
      extra: { duration, args: params.args },
    });
  }

  return result;
});

export { prisma };

これでスロークエリは逃しません。

RUM vs 合成モニタリング

RUM(Real User Monitoring)
Sentry や Datadog などで実ユーザーのパフォーマンスデータを収集。実シーン(ネットワーク・デバイス差)を反映する利点がある一方、問題が起きてからしか分からない受動的な方式。

合成モニタリング(Synthetic Monitoring)
Checkly や Pingdom などで世界各地から定期的にサイトへアクセスをシミュレート。能動的に問題を発見できる一方、すべてのユーザーシーンをカバーできません。

両方の併用がベスト

  • RUM でユーザー体験指標を把握
  • 合成モニタリングで可用性とクリティカルフロー(ログイン、決済)を監視

Checkly で 5 分ごとに 5 地域からホームページとログイン API にアクセスし、タイムアウトや失敗時に即アラートを設定しています。

アラート設定 — 最速で問題を検知

Slack 連携:チームに最速で届ける

Sentry の Slack 連携は簡単。Settings → Integrations → Slack で認可し、通知先チャネルを選ぶだけ。

ただしデフォルトではすべてのエラーが通知され、すぐにノイズになります。アラートルールの設定が必要

Sentry プロジェクト設定で:

  1. Alerts → Create Alert Rule
  2. トリガー条件を選択:
    • エラー率アラート:「10 分間でエラー数 > 50」
    • 新規エラーアラート:「初回出現エラーを即通知」
    • パフォーマンス退行:「API P95 応答時間 > 1s」
  3. Action を選択:Send a notification via Slack

Slack メッセージ例:

🚨 Production Error Spike

Project: my-nextjs-app
Environment: production
Error: TypeError: Cannot read property 'id' of undefined
Events: 127 events in 10 minutes

View in Sentry: https://sentry.io/...

リンクをクリックすれば Sentry で詳細とスタックを確認できます。

アラートの優先度分け:疲労を避ける

すべての問題が同じ緊急度ではありません。私の分類:

P0 — 重大(即対応)

  • サービス完全停止
  • 決済機能の失敗
  • DB 接続断

通知:電話(PagerDuty)+ Slack @channel

P1 — 重要(1 時間以内に対応)

  • コア機能の異常
  • エラー率の急増(10 分間 > 100 回)
  • API 応答時間 P95 > 3s

通知:Slack 開発チャネル

P2 — 一般(勤務時間内に対応)

  • 小規模エラー(< 10 回/時間)
  • 非クリティカル機能の異常
  • サードパーティスクリプトのエラー

通知:メール日次サマリー

設定例(Sentry Alert Rule):

// P0 アラート:決済失敗
{
  conditions: [
    { type: 'event.tag', key: 'feature', value: 'payment' },
    { type: 'event.level', value: 'error' }
  ],
  frequency: 'every event',  // 毎回通知
  actions: [
    { type: 'slack', channel: '#critical-alerts', mention: '@channel' },
    { type: 'pagerduty', service: 'payments' }
  ]
}

// P1 アラート:エラー率急増
{
  conditions: [
    { type: 'event.count', value: 100, interval: '10m' }
  ],
  frequency: 'once per issue',  // 各問題につき 1 回だけ
  actions: [
    { type: 'slack', channel: '#alerts-dev' }
  ]
}

アラートノイズ低減のテクニック

監視を始めたばかりは、アラートに埋もれるかもしれません。いくつかのテクニック:

1. 既知の問題を無視

開発環境の HMR エラーやサードパーティスクリプトの異常など、ノイズはフィルタ:

// sentry.client.config.ts
Sentry.init({
  ignoreErrors: [
    // ブラウザ拡張機能のエラー
    /chrome-extension/,
    /moz-extension/,
    // サードパーティスクリプト
    /google-analytics/,
    // 開発環境 HMR
    /HMR/,
  ],
  denyUrls: [
    // 特定ドメインのスクリプトエラーを無視
    /extensions\//i,
    /^chrome:\/\//i,
  ],
});

2. 重複アラートの統合

同じエラーは 10 分間に 1 回だけ通知し、チャネルの洪水を防ぎます。Sentry の “Issue Grouping” が類似エラーを自動統合します。

3. サイレント期間の設定

デプロイ中は短時間エラーがスパイクすることがあり、“Mute for 10 minutes” を設定できます。

4. フィンガープリント(Fingerprint)の利用

カスタムエラー分组ルールで、同根のエラーを統合:

Sentry.captureException(error, {
  fingerprint: ['database-connection-error', databaseName],
});

異なる DB の接続エラーは分離され、原因特定が容易になります。

実践事例 — 完全なモニタリング体制の構築

EC サイトのモニタリングアーキテクチャ

昨年、EC サイトのモニタリング改修を支援しました。完全な構成を共有します:

背景

  • 日次 UV 8 万
  • ピーク時 QPS 3000+
  • 主な課題:決済の偶発失敗、トップページの読み込み遅延

モニタリング構成

┌─────────────┐
│   Next.js   │
│   フロント/SSR  │
└──────┬──────┘

       ├─ Sentry(エラー + パフォーマンス)
       ├─ Pino(構造化ログ)→ Datadog
       ├─ Web Vitals → Sentry
       └─ Checkly(合成モニタリング)

重要設定

  1. ユーザー行動追跡
// lib/tracking.ts
import * as Sentry from '@sentry/nextjs';

export function trackCheckoutStep(step: string, data: any) {
  Sentry.addBreadcrumb({
    category: 'checkout',
    message: `Checkout step: ${step}`,
    data,
    level: 'info',
  });
}

// チェックアウトフローで呼び出し
trackCheckoutStep('add_to_cart', { productId, price });
trackCheckoutStep('proceed_to_payment', { cartTotal });
trackCheckoutStep('payment_submitted', { method: 'credit_card' });

決済失敗時に、ユーザーの購入パス全体が見えます。

  1. 決済モニタリング
// app/api/payment/route.ts
export async function POST(request: Request) {
  const log = logger.child({ action: 'payment' });

  try {
    const result = await processPayment(data);

    log.info({ orderId, amount, method }, 'Payment succeeded');

    return Response.json({ success: true, orderId });
  } catch (error) {
    log.error({ error, orderId, userId }, 'Payment failed');

    // P0 アラート
    Sentry.captureException(error, {
      tags: { feature: 'payment', severity: 'critical' },
      level: 'fatal',
    });

    return Response.json({ error: 'Payment failed' }, { status: 500 });
  }
}

決済失敗はすべて即座にチームへ通知されます。

  1. パフォーマンス基線の設定

Sentry Performance Monitoring で基線を確立:

  • トップページ LCP < 2s
  • 商品詳細ページ LCP < 2.5s
  • API /api/products P95 < 500ms

基線超過で自動アラート。

効果

  • 障害検知時間:平均 40 分 → 3 分
  • 決済失敗率:0.8% → 0.2%
  • トップページ LCP:3.2s → 1.8s に改善

モニタリング Checklist

最後に、プロジェクトを確認するためのチェックリスト:

**エラー監視**
- [ ] Sentry を設定しテスト済み
- [ ] Source Maps のアップロード成功
- [ ] global-error.tsx を作成(App Router)
- [ ] Server Actions にエラー処理をラップ
- [ ] 無視ルールを設定(ノイズフィルタ)

**ログ管理**
- [ ] ログライブラリを連携(Pino/Winston)
- [ ] 本番環境は JSON 形式で出力
- [ ] ログに correlationId を含める
- [ ] ログレベルを正しく設定(本番 INFO)
- [ ] ログを集約プラットフォームに接続

**パフォーマンス監視**
- [ ] Web Vitals レポートを有効化
- [ ] Core Web Vitals 達成(LCP<2.5s, INP<200ms, CLS<0.1)
- [ ] 重要 API にパフォーマンストレースを追加
- [ ] スロークエリ監視を設定
- [ ] 合成モニタリングを設定(任意)

**アラート設定**
- [ ] Slack/メールアラートをテスト済み
- [ ] アラートルールを優先度別に分類
- [ ] アラートノイズ低減ルールを設定
- [ ] チーム全員がアラートフローを把握
- [ ] P0 イベントの担当者を明確化

**継続的改善**
- [ ] 週次で監視データをレビュー
- [ ] エラートレンド分析(増加しているエラー)
- [ ] パフォーマンス退行検知(遅くなったページ)
- [ ] アラートルールを定期最適化

まとめ

受動的な火消しから能動的な検知へ。モニタリングは本番環境へのコントロールを与えてくれます。

構築した監視体制を振り返ると:

  • Sentry がエラー追跡とパフォーマンス監視を担当し、ユーザー操作の再生も可能
  • Pino が構造化ログを提供し、correlationId でリクエスト全体を串刺し
  • Web Vitals がユーザー体験指標を追い、SEO ランキングに直結
  • Slack アラート がチームに最速で問題を届け、優先度分けで疲労を防ぐ

より重要なのはマインドセットの転換:モニタリングは「あれば便利」ではなく、本番環境のエアバッグ。事故が起きてからエアバッグを付ける人はいません。本番障害が起きてから監視を思い出すのも同じです。

今日から始めましょう。まだ監視がないなら:

  1. 今週末 2 時間で Sentry を導入し、基本エラー追跡を設定
  2. 来週、構造化ログと correlationId を追加
  3. その次の週、Slack アラートとパフォーマンス基線を設定

一気に完璧を目指さず、まず基本のエラー監視を動かし、段階的に拡張してください。本番障害のたびに「監視でより早く検知できたか?」と自問し、継続的に改善すれば、モニタリングは最も頼れる味方になります。

この記事が役に立ったら、チームにも共有してください。モニタリングはチーム全体の取り組みです。

Next.js アプリが安定稼働し続けることを願っています。(現実はそう甘くないので、モニタリングは本当に重要です😄)

Next.js 本番環境モニタリング完全設定フロー

Sentry 連携からログ管理、パフォーマンス監視、アラート設定までの完全手順

⏱️ 目安時間: 3 時間

  1. 1

    ステップ1: Sentry エラー追跡を連携

    インストール:
    ```bash
    npm install @sentry/nextjs
    ```

    初期化:
    ```bash
    npx @sentry/wizard@latest -i nextjs
    ```

    クライアント設定:
    ```ts
    // sentry.client.config.ts
    import * as Sentry from '@sentry/nextjs'

    Sentry.init({
    dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
    environment: process.env.NODE_ENV,
    tracesSampleRate: 1.0,
    })
    ```

    サーバー設定:
    ```ts
    // sentry.server.config.ts
    import * as Sentry from '@sentry/nextjs'

    Sentry.init({
    dsn: process.env.SENTRY_DSN,
    environment: process.env.NODE_ENV,
    tracesSampleRate: 1.0,
    })
    ```

    ポイント:
    • クライアントとサーバーは別々に設定
    • tracesSampleRate でサンプリング率を制御
    • 環境変数を設定
  2. 2

    ステップ2: 構造化ログを設定

    correlationId でリクエストを紐付け:
    ```ts
    // middleware.ts
    import { v4 as uuidv4 } from 'uuid'

    export function middleware(request: NextRequest) {
    const correlationId = request.headers.get('x-correlation-id') || uuidv4()

    const response = NextResponse.next()
    response.headers.set('x-correlation-id', correlationId)

    return response
    }
    ```

    ログでの利用:
    ```ts
    import { headers } from 'next/headers'

    export async function handler() {
    const headersList = headers()
    const correlationId = headersList.get('x-correlation-id')

    console.log({
    correlationId,
    message: 'User action',
    timestamp: new Date().toISOString(),
    })
    }
    ```

    ポイント:
    • 各リクエストに一意 ID を付与
    • すべてのログに correlationId を含める
    • リクエスト全体の追跡が容易になる
  3. 3

    ステップ3: パフォーマンス監視を設定

    Sentry APM:
    ```ts
    Sentry.init({
    tracesSampleRate: 1.0, // 100% サンプリング
    integrations: [
    new Sentry.Integrations.Http({ tracing: true }),
    ],
    })
    ```

    カスタムパフォーマンス監視:
    ```ts
    const transaction = Sentry.startTransaction({
    op: 'http.server',
    name: 'API Route',
    })

    try {
    // ビジネスロジック
    await processRequest()
    } finally {
    transaction.finish()
    }
    ```

    ポイント:
    • tracesSampleRate でサンプリング率を制御
    • API 応答時間を監視
    • パフォーマンスボトルネックを特定
  4. 4

    ステップ4: アラートを設定

    Sentry アラート:
    • Sentry Dashboard でアラートルールを設定
    • エラー閾値を設定
    • 通知チャネル(Slack、メールなど)を設定

    Slack 連携:
    ```ts
    // Sentry Dashboard で設定
    // Webhook URL: https://hooks.slack.com/services/...
    ```

    メールアラート:
    • Sentry Dashboard で設定
    • 受信者を設定
    • アラート条件を設定

    ポイント:
    • 適切なアラート閾値を設定
    • アラート疲れを避ける
    • アラートに迅速に対応

FAQ

なぜ Next.js には専用のモニタリングが必要なのか?
理由は Next.js の「三頭怪」特性にあります。

同一アプリが 3 箇所で動きます:
• クライアント(Browser):ユーザーのブラウザ上の React コンポーネント
• サーバー(Node.js):SSR レンダリング、API Routes、Server Actions
• エッジ(Edge Runtime):ミドルウェア、エッジ関数

従来のフロントエンド監視はクライアントのエラーしか見えません。

SSR ブラックボックス:
• サーバー側レンダリングでエラーが起きると、ユーザーには 500 ページしか見えない
• スタックトレースもコンテキストもない
• Sentry の分散トレーシングで初めて原因を特定できる

実例:
• ユーザーから「ページの読み込みが遅く、500 が表示される」と報告
• ブラウザの Network パネルではリクエストが遅いことしか分からない
• Sentry 導入後、サーバー側のサードパーティ API 応答が 200ms から 8 秒に急増していたことが判明

解決策:クライアント、サーバー、エッジをカバーする包括的な監視体制が必要です。
Sentry はどう連携する?
インストール:
```bash
npm install @sentry/nextjs
```

初期化:
```bash
npx @sentry/wizard@latest -i nextjs
```

クライアント設定:
```ts
// sentry.client.config.ts
import * as Sentry from '@sentry/nextjs'

Sentry.init({
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
environment: process.env.NODE_ENV,
tracesSampleRate: 1.0,
})
```

サーバー設定:
```ts
// sentry.server.config.ts
import * as Sentry from '@sentry/nextjs'

Sentry.init({
dsn: process.env.SENTRY_DSN,
environment: process.env.NODE_ENV,
tracesSampleRate: 1.0,
})
```

ポイント:
• クライアントとサーバーは別々に設定
• tracesSampleRate でサンプリング率を制御
• 環境変数(NEXT_PUBLIC_SENTRY_DSN、SENTRY_DSN)を設定
構造化ログはどう設定する?
correlationId でリクエストを紐付け:

middleware で生成:
```ts
import { v4 as uuidv4 } from 'uuid'

export function middleware(request: NextRequest) {
const correlationId = request.headers.get('x-correlation-id') || uuidv4()

const response = NextResponse.next()
response.headers.set('x-correlation-id', correlationId)

return response
}
```

ログでの利用:
```ts
import { headers } from 'next/headers'

export async function handler() {
const headersList = headers()
const correlationId = headersList.get('x-correlation-id')

console.log({
correlationId,
message: 'User action',
timestamp: new Date().toISOString(),
})
}
```

メリット:
• 各リクエストに一意 ID を付与
• すべてのログに correlationId を含める
• リクエスト全体の追跡が容易
• 問題の迅速な特定

ポイント:middleware で生成し、ログで利用して紐付ける。
パフォーマンス監視はどう設定する?
Sentry APM:
```ts
Sentry.init({
tracesSampleRate: 1.0, // 100% サンプリング(本番は 0.1 推奨)
integrations: [
new Sentry.Integrations.Http({ tracing: true }),
],
})
```

カスタムパフォーマンス監視:
```ts
const transaction = Sentry.startTransaction({
op: 'http.server',
name: 'API Route',
})

try {
// ビジネスロジック
await processRequest()
} finally {
transaction.finish()
}
```

監視指標:
• API 応答時間
• データベースクエリ時間
• サードパーティ API 呼び出し時間
• ページ読み込み時間

ポイント:
• tracesSampleRate でサンプリング率を制御(本番は 0.1 推奨)
• クリティカルパスを監視
• パフォーマンスボトルネックを特定
アラートはどう設定する?
Sentry アラート:
• Sentry Dashboard でアラートルールを設定
• エラー閾値を設定(例:5 分間でエラー数 > 10)
• 通知チャネル(Slack、メールなど)を設定

Slack 連携:
• Sentry Dashboard で Webhook URL を設定
• アラート条件を設定
• アラートをテスト

メールアラート:
• Sentry Dashboard で設定
• 受信者を設定
• アラート条件を設定

アラートルールの推奨:
• エラー率が閾値を超過
• 応答時間が閾値を超過
• 特定のエラータイプ
• 新規エラーの出現

ポイント:
• 適切なアラート閾値を設定
• アラート疲れを避ける
• アラートに迅速に対応

推奨:まず基本的なエラー監視を動かし、段階的に拡張する。
モニタリングのベストプラクティスは?
段階的導入:
1. 今週末に 2 時間かけて Sentry を導入し、基本的なエラー追跡を設定
2. 来週、構造化ログと correlationId を追加
3. その次の週、Slack アラートとパフォーマンス基線を設定

一気に完璧を目指さず、まず基本的なエラー監視を動かしてから段階的に拡張しましょう。

継続的改善:
• 本番障害のたびに自問:「監視でより早く検知できたか?」
• 実情に合わせてアラート閾値を調整
• 監視設定を定期的に見直す

重要指標:
• エラー率
• 応答時間
• ユーザーへの影響範囲
• 復旧時間

推奨:
• モニタリングはチーム全体の取り組み
• 監視データを定期的に共有
• 監視体制を継続的に改善

覚えておいて:モニタリングは一度きりの作業ではなく、継続的なプロセスです。

8分で読めます · 公開日: 2025年12月20日 · 更新日: 2026年6月8日

関連記事

コメント

GitHubアカウントでログインしてコメントできます