Chuyển đến nội dung chính

レッスン 22: 本番環境のベスト プラクティスとモニタリング

エラー追跡 (セントリー)。ロギングのベストプラクティス。ヘルスチェック、正常なシャットダウン。セキュリティヘッダー。レート制限、監視ダッシュボード。

💻 プログラミング — レッスン 22 レッスン 22: 本番環境のベスト プラクティスと モニタリング

React と Next.js: 基本から高度まで

パート 6: テスト、展開、実稼働

xdev.asia

1. Sentry によるエラー追跡

npx @sentry/wizard@latest -i nextjs
// sentry.client.config.ts
import * as Sentry from '@sentry/nextjs';

Sentry.init({
  dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
  tracesSampleRate: 0.1, // 10% of transactions
  replaysSessionSampleRate: 0.1,
  replaysOnErrorSampleRate: 1.0,
  integrations: [
    Sentry.replayIntegration(),
    Sentry.feedbackIntegration({ colorScheme: 'system' }),
  ],
});
// app/global-error.tsx
'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.captureException(error);
  }, [error]);

  return (
    <html>
      <body>
        <h2>Đã xảy ra lỗi!</h2>
        <button onClick={reset}>Thử lại</button>
      </body>
    </html>
  );
}

2. ロギングのベストプラクティス

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

export const logger = pino({
  level: process.env.LOG_LEVEL || 'info',
  transport: process.env.NODE_ENV === 'development'
    ? { target: 'pino-pretty' }
    : undefined,
  formatters: {
    level(label) { return { level: label }; },
  },
  base: {
    env: process.env.NODE_ENV,
    service: 'nextjs-app',
  },
});

// Sử dụng
logger.info({ userId: '123', action: 'login' }, 'User logged in');
logger.error({ err: error, requestId }, 'Request failed');

3. セキュリティヘッダー

// next.config.ts
const securityHeaders = [
  {
    key: 'X-DNS-Prefetch-Control',
    value: 'on',
  },
  {
    key: 'Strict-Transport-Security',
    value: 'max-age=63072000; includeSubDomains; preload',
  },
  {
    key: 'X-Content-Type-Options',
    value: 'nosniff',
  },
  {
    key: 'X-Frame-Options',
    value: 'DENY',
  },
  {
    key: 'X-XSS-Protection',
    value: '1; mode=block',
  },
  {
    key: 'Referrer-Policy',
    value: 'strict-origin-when-cross-origin',
  },
  {
    key: 'Permissions-Policy',
    value: 'camera=(), microphone=(), geolocation=()',
  },
  {
    key: 'Content-Security-Policy',
    value: "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline';",
  },
];

const config = {
  async headers() {
    return [{ source: '/:path*', headers: securityHeaders }];
  },
};

4. ヘルスチェック

// app/api/health/route.ts
import { NextResponse } from 'next/server';
import { db } from '@/lib/db';

export async function GET() {
  try {
    // Check database
    await db.$queryRaw`SELECT 1`;

    return NextResponse.json({
      status: 'healthy',
      timestamp: new Date().toISOString(),
      uptime: process.uptime(),
      checks: {
        database: 'ok',
        memory: process.memoryUsage(),
      },
    });
  } catch (error) {
    return NextResponse.json(
      { status: 'unhealthy', error: 'Database connection failed' },
      { status: 503 }
    );
  }
}

// app/api/ready/route.ts — Kubernetes readiness probe
export async function GET() {
  // Check all external dependencies
  const checks = await Promise.allSettled([
    db.$queryRaw`SELECT 1`,
    fetch(process.env.REDIS_URL + '/ping'),
  ]);

  const allHealthy = checks.every(c => c.status === 'fulfilled');
  return NextResponse.json(
    { ready: allHealthy },
    { status: allHealthy ? 200 : 503 }
  );
}

5. レート制限

// middleware.ts — Simple rate limiting
import { NextRequest, NextResponse } from 'next/server';

const rateLimit = new Map<string, { count: number; resetTime: number }>();

export function middleware(request: NextRequest) {
  if (!request.nextUrl.pathname.startsWith('/api')) {
    return NextResponse.next();
  }

  const ip = request.headers.get('x-forwarded-for') || 'unknown';
  const now = Date.now();
  const windowMs = 60 * 1000; // 1 minute
  const maxRequests = 100;

  const record = rateLimit.get(ip);

  if (!record || now > record.resetTime) {
    rateLimit.set(ip, { count: 1, resetTime: now + windowMs });
    return NextResponse.next();
  }

  if (record.count >= maxRequests) {
    return NextResponse.json(
      { error: 'Too many requests' },
      { status: 429, headers: { 'Retry-After': '60' } }
    );
  }

  record.count++;
  return NextResponse.next();
}

6. 制作チェックリスト

カテゴリアイテムステータス
セキュリティ設定されたセキュリティヘッダー☐
セキュリティCSRF 保護 (サーバー アクション)☐
セキュリティレート制限が有効になっています☐
セキュリティ環境変数が検証されました☐
パフォーマンス最適化された画像 (次/画像)☐
パフォーマンスフォントの最適化 (次へ/フォント)☐
パフォーマンスバンドルの分析と最適化☐
モニタリングエラー追跡 (セントリー)☐
モニタリング構造化されたロギング☐
モニタリングヘルスチェックエンドポイント☐
デプロイDocker のマルチステージ ビルド☐
デプロイCI/CD パイプライン☐
デプロイCI でのデータベースの移行☐
SEOサイトマップとrobots.txt☐
SEOOpenGraph と構造化データ☐

🎉 おめでとうございます! React & Next.js コースの基本から上級までを完了しました。知識を実際のプロジェクトに応用してください。