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

レッスン 22: 本番環境の監視とベスト プラクティス

エラー追跡 (Sentry)、分析。構造化されたロギング、ヘルスチェック。セキュリティのベスト プラクティス (CORS、CSP、XSS)。 pnpm ワークスペース、共有パッケージを使用した Monorepo。デザインシステム。

💻 プログラミング — レッスン 22 レッスン 22: 生産監視と最適化 実践

Vue.js と Nuxt: 基本から高度まで

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

xdev.asia

1. Sentry によるエラー追跡

npm i @sentry/vue @sentry/node
// plugins/sentry.client.ts
import * as Sentry from '@sentry/vue'

export default defineNuxtPlugin((nuxtApp) => {
  const config = useRuntimeConfig()

  Sentry.init({
    app: nuxtApp.vueApp,
    dsn: config.public.sentryDsn,
    environment: config.public.env,
    integrations: [
      Sentry.browserTracingIntegration({
        router: useRouter(),
      }),
      Sentry.replayIntegration(),
    ],
    tracesSampleRate: 0.1,
    replaysSessionSampleRate: 0.05,
  })
})

// server/plugins/sentry.ts
import * as Sentry from '@sentry/node'

export default defineNitroPlugin((nitro) => {
  Sentry.init({
    dsn: useRuntimeConfig().sentryDsn,
    tracesSampleRate: 0.2,
  })

  nitro.hooks.hook('error', (error) => {
    Sentry.captureException(error)
  })
})

2. 構造化されたロギング

// server/utils/logger.ts
import { consola, createConsola } from 'consola'

export const logger = createConsola({
  level: process.env.NODE_ENV === 'production' ? 3 : 4,
  formatOptions: {
    date: true,
    colors: process.env.NODE_ENV !== 'production',
  },
})

// Sử dụng
logger.info('User logged in', { userId: user.id, ip: getRequestIP(event) })
logger.error('Payment failed', { orderId, error: err.message })

3. ヘルスチェック

// server/api/health.get.ts
export default defineEventHandler(async () => {
  const checks = {
    status: 'ok',
    timestamp: new Date().toISOString(),
    uptime: process.uptime(),
    memory: process.memoryUsage(),
    checks: {} as Record<string, string>,
  }

  // Database check
  try {
    await db.execute(sql`SELECT 1`)
    checks.checks.database = 'ok'
  } catch {
    checks.checks.database = 'error'
    checks.status = 'degraded'
  }

  // Redis check
  try {
    await redis.ping()
    checks.checks.redis = 'ok'
  } catch {
    checks.checks.redis = 'error'
    checks.status = 'degraded'
  }

  return checks
})

4. セキュリティのベストプラクティス

// nuxt.config.ts
export default defineNuxtConfig({
  security: {
    headers: {
      contentSecurityPolicy: {
        'default-src': ["'self'"],
        'script-src': ["'self'", "'nonce-{{nonce}}'"],
        'style-src': ["'self'", "'unsafe-inline'"],
        'img-src': ["'self'", 'data:', 'https:'],
      },
      xFrameOptions: 'DENY',
      xContentTypeOptions: 'nosniff',
      referrerPolicy: 'strict-origin-when-cross-origin',
    },
    corsHandler: {
      origin: ['https://myapp.com'],
      methods: ['GET', 'POST', 'PUT', 'DELETE'],
    },
    rateLimiter: {
      tokensPerInterval: 100,
      interval: 60000,
    },
  },
})

// server/middleware/csrf.ts
export default defineEventHandler((event) => {
  if (['POST', 'PUT', 'DELETE'].includes(event.method)) {
    const token = getHeader(event, 'x-csrf-token')
    const sessionToken = getCookie(event, 'csrf-token')
    if (!token || token !== sessionToken) {
      throw createError({ statusCode: 403, message: 'Invalid CSRF token' })
    }
  }
})

5. pnpm ワークスペースを使用したモノリポジトリ

# pnpm-workspace.yaml
packages:
  - 'apps/*'
  - 'packages/*'
# Cấu trúc
monorepo/
├── pnpm-workspace.yaml
├── apps/
│   ├── web/          # Nuxt app chính
│   └── admin/        # Nuxt admin panel
├── packages/
│   ├── ui/           # Shared Vue components
│   ├── utils/        # Shared utilities
│   └── config/       # Shared configs (eslint, tsconfig)
// packages/ui/package.json
{
  "name": "@myapp/ui",
  "exports": {
    ".": "./src/index.ts",
    "./components/*": "./src/components/*.vue"
  }
}

// apps/web/package.json
{
  "dependencies": {
    "@myapp/ui": "workspace:*",
    "@myapp/utils": "workspace:*"
  }
}

6. デザインシステム

<!-- packages/ui/src/components/BaseButton.vue -->
<script setup lang="ts">
interface Props {
  variant?: 'primary' | 'secondary' | 'danger'
  size?: 'sm' | 'md' | 'lg'
  loading?: boolean
  disabled?: boolean
}

const props = withDefaults(defineProps<Props>(), {
  variant: 'primary',
  size: 'md',
  loading: false,
  disabled: false,
})

const emit = defineEmits<{ click: [event: MouseEvent] }>()

const classes = computed(() => [
  'btn',
  `btn-${props.variant}`,
  `btn-${props.size}`,
  { 'btn-loading': props.loading },
])
</script>

<template>
  <button
    :class="classes"
    :disabled="disabled || loading"
    @click="emit('click', $event)"
  >
    <span v-if="loading" class="spinner" />
    <slot />
  </button>
</template>

7. 制作チェックリスト

カテゴリチェックリスト
セキュリティCSP ヘッダー、CSRF、レート制限、入力検証
パフォーマンスLighthouse ≥ 90、バンドル < 200KB、画像の最適化
モニタリングセントリー、ヘルスチェック、稼働時間監視
SEOメタタグ、sitemap.xml、robots.txt、構造化データ
CI/CD自動テスト、ステージング環境、ロールバック計画
バックアップデータベースのバックアップ、環境シークレットのローテーション、災害復旧

🎉 シリーズを完了する Vue.js と Nuxt: 基本から高度まで!