404 Not Found

404 Not Found


nginx

プラグインシステム

CharlieはMegaShopにStripe決済SDKを統合する必要があり, グローバルロギングサービスも欲しています。Stripeはクライアント側でのみ読み込まれ, ログはサーバー側で記録されます。従来は複数箇所で手動インポートと初期化が必要でしたが, Nuxt 3のプラグインシステムは全プロセスを自動化します。一度登録すれば, どこでも利用可能です。

1. 学ぶ内容


2. アーキテクトのある本当の話

(1) 課題:サードパーティライブラリの初期化がコード全体に散在

CharlieのMegaShopはStripe決済を統合する必要があります。AliceはカートページでStripe.jsを読み込み, Bobは注文ページで再度読み込みました。結果として重複読み込みが発生し, 一部のページでは読み込み忘れにより決済ボタンが使えません。ロギングツールも同様で, 各コンポーネントでロガーを手動インポートする必要があります。

(2) Nuxtプラグインシステムの解決策

Nuxt 3プラグインは自動実行され, provideを通じてグローバルに注入されます:

TYPESCRIPT
// plugins/stripe.client.ts
export default defineNuxtPlugin(() => {
  const stripe = Stripe(config.public.stripePublishableKey)
  return { provide: { stripe } }
})

(3) メリット:一度の登録でグローバルアクセス

StripeとLoggerはプラグイン内で一度だけ初期化され, すべてのコンポーネントはuseNuxtApp().$stripeでアクセスできます。重複読み込みや手動インポートは不要です。


3. プラグインの自動登録

(1) プラグイン登録と実行タイミング

100%
sequenceDiagram
    participant N as Nuxt App
    participant P1 as 01-env.server.ts
    participant P2 as 02-stripe.client.ts
    participant P3 as 03-logger.ts
    participant C as Components

    N->>P1: サーバープラグインを先に実行
    N->>P2: ハイドレーション時にクライアントプラグインを実行
    N->>P3: ユニバーサルプラグインを実行
    N->>C: コンポーネントは$stripe, $loggerを使用可能

(2) プラグインの種類と命名規則

名前 実行環境 用途
plugins/xxx.ts SSR + クライアント側 汎用プラグイン
plugins/xxx.server.ts サーバーのみ データベース/キー
plugins/xxx.client.ts クライアントのみ ブラウザSDK

(1) ▶ サンプル:汎用ログプラグイン

TYPESCRIPT
// plugins/logger.ts
export default defineNuxtPlugin(() => {
  const logger = {
    info: (message: string, data?: any) => {
      console.log(`[INFO] ${message}`, data || '')
    },
    warn: (message: string, data?: any) => {
      console.warn(`[WARN] ${message}`, data || '')
    },
    error: (message: string, data?: any) => {
      console.error(`[ERROR] ${message}`, data || '')
    }
  }

  return {
    provide: {
      logger
    }
  }
})

出力:

TEXT
// 実行成功

4. defineNuxtPluginの詳細解説

(1) Provide/Injectメカニズム

役割 API 説明
プラグイン提供側 provide: { xxx } NuxtAppに$xxxを注入
コンポーネントでの使用 useNuxtApp().$xxx 注入されたサービスにアクセス
Composableでの使用 useNuxtApp().$xxx Composableからアクセス

(1) ▶ サンプル:Stripe決済プラグイン (クライアント側)

TYPESCRIPT
// plugins/stripe.client.ts
export default defineNuxtPlugin(() => {
  const config = useRuntimeConfig()
  const stripe = window.Stripe(config.public.stripePublishableKey)

  return {
    provide: {
      stripe
    }
  }
})

出力:

TEXT
// 実行成功

(2) ▶ サンプル:コンポーネントでプラグインを使用

VUE
<!-- pages/checkout.vue -->
<template>
  <div>
    <h1>Checkout - ${{ total }} USD</h1>
    <div ref="cardElement"></div>
    <button @click="processPayment" :disabled="processing">
      Pay ${{ total }} USD
    </button>
  </div>
</template>

<script setup lang="ts">
const { $stripe } = useNuxtApp()
const { $logger } = useNuxtApp()

const cart = useState<any[]>('cart')
const total = computed(() => cart.value.reduce((s, i) => s + i.price * i.quantity, 0))
const processing = ref(false)

async function processPayment() {
  processing.value = true
  try {
    $logger.info('Processing payment', { amount: total.value })
    // Stripeで決済
    const result = await $stripe.confirmCardPayment('{PAYMENT_INTENT_SECRET}')
    if (result.error) {
      $logger.error('Payment failed', result.error)
    } else {
      $logger.info('Payment succeeded')
      navigateTo('/order/success')
    }
  } finally {
    processing.value = false
  }
}
</script>

出力:

TEXT
// 実行成功

(3) ▶ サンプル:サーバー側データベースプラグイン

TYPESCRIPT
// plugins/database.server.ts
export default defineNuxtPlugin(() => {
  const config = useRuntimeConfig()

  // サーバーでのみ実行 - プライベートキーによるデータベース接続
  const db = createDatabaseConnection(config.databaseUrl)

  return {
    provide: {
      db
    }
  }
})

出力:

TEXT
// 実行成功

5. サーバー側とクライアント側プラグインの区別

(1) ▶ サンプル:通知プラグイン (クライアント側, ブラウザAPI使用)

TYPESCRIPT
// plugins/notification.client.ts
export default defineNuxtPlugin(() => {
  function requestPermission() {
    if ('Notification' in window) {
      Notification.requestPermission()
    }
  }

  function send(title: string, body: string) {
    if (Notification.permission === 'granted') {
      new window.Notification(title, { body, icon: '/favicon.ico' })
    }
  }

  // 初回訪問時に自動で許可をリクエスト
  requestPermission()

  return { provide: { notification: { send, requestPermission } } }
})

出力:

TEXT
// 実行成功

(2) ▶ サンプル:環境検出プラグイン (汎用)

TYPESCRIPT
// plugins/env.ts
export default defineNuxtPlugin(() => {
  const isServer = import.meta.server
  const isClient = import.meta.client
  const isDev = import.meta.dev

  return {
    provide: {
      env: { isServer, isClient, isDev }
    }
  }
})

出力:

TEXT
// 実行成功

(1) サーバー側 vs クライアント側プラグインの比較

項目 .server.ts .client.ts .ts (汎用)
SSRでの実行
クライアントでの実行
ブラウザAPI ❌ 利用不可 ✅ 利用可能 ⚠️ 事前チェック必要
Node.js API ✅ 利用可能 ❌ 利用不可 ⚠️ 環境に依存
プライベートデータ ✅ 安全 ❌ 露出の可能性 ⚠️ 注意が必要

6. プラグインの実行順序

(1) ソートルール

ルール 実行順序
ファイル名のアルファベット順 01-aaa.ts → 02-bbb.ts 01が先に実行
数字プレフィックス制御 01-env.ts → 02-db.ts → 03-api.ts 数字順
serverがclientより先 db.server.ts → stripe.client.ts SSR時にserverが先
nuxt.configのプラグインは最後 モジュール経由で注入されたプラグイン フレームワークプラグインが先に実行

(1) ▶ サンプル:プラグインの実行順序を制御

TEXT
plugins/
├── 01-runtime-env.ts       # 1番目:環境セットアップ
├── 02-database.server.ts   # 2番目:データベース接続
├── 03-logger.ts            # 3番目:ロガー初期化
├── 04-stripe.client.ts     # 4番目:Stripe初期化
└── 05-analytics.client.ts  # 5番目:アナリティクス初期化

出力:

TEXT
実行成功

(2) ▶ サンプル:プラグイン内で他のプラグインを使用

TYPESCRIPT
// plugins/05-analytics.client.ts
export default defineNuxtPlugin((nuxtApp) => {
  // 先に登録されたプラグインにアクセス
  const { $logger } = nuxtApp

  $logger.info('Analytics plugin initialized')

  const analytics = {
    track(event: string, data?: any) {
      $logger.info(`Track: ${event}`, data)
      // アナリティクスサービスに送信
    },
    pageView(path: string) {
      $logger.info(`Page view: ${path}`)
    }
  }

  // ページビューを自動トラッキング
  nuxtApp.hook('page:finish', () => {
    analytics.pageView(window.location.pathname)
  })

  return { provide: { analytics } }
})

出力:

TEXT
// 実行成功

7. 総合例:MegaShopプラグインシステム

TYPESCRIPT
// plugins/01-config.ts - ランタイム設定ヘルパー
export default defineNuxtPlugin(() => {
  const config = useRuntimeConfig()
  return {
    provide: {
      config: {
        apiBase: config.public.apiBase,
        currency: 'USD',
        locale: 'en-US',
        maxCartItems: 99
      }
    }
  }
})
TYPESCRIPT
// plugins/02-logger.ts - 構造化ログ
export default defineNuxtPlugin(() => {
  const logger = {
    info(msg: string, ctx?: Record<string, any>) {
      console.log(JSON.stringify({ level: 'info', msg, ctx, ts: Date.now() }))
    },
    error(msg: string, ctx?: Record<string, any>) {
      console.error(JSON.stringify({ level: 'error', msg, ctx, ts: Date.now() }))
    }
  }
  return { provide: { logger } }
})
TYPESCRIPT
// plugins/03-stripe.client.ts - 決済SDK
export default defineNuxtPlugin(() => {
  const config = useRuntimeConfig()
  const stripe = window.Stripe(config.public.stripePublishableKey)
  return { provide: { stripe } }
})
TYPESCRIPT
// plugins/04-notification.client.ts - ブラウザ通知
export default defineNuxtPlugin(() => {
  const notify = {
    send(title: string, body: string) {
      if ('Notification' in window && Notification.permission === 'granted') {
        new Notification(title, { body })
      }
    }
  }
  return { provide: { notify } }
})

❓ よくある質問

Q プラグインとComposableの違いは何ですか?
A プラグインはアプリ起動時に自動的に一度実行され, サードパーティライブラリの初期化に使用されます。Composableはコンポーネント内でオンデマンドで呼び出され, ロジックの再利用に使用されます。Stripeはプラグインで初期化し, 価格フォーマットはComposableで行います。
Q 注入されたサービス名に$プレフィックスがつくのはなぜですか?
A Nuxtは注入されたサービスに$プレフィックス ($stripe/$logger)を付けることを推奨しており, コンポーネント内の変数との競合を避けるためです。これはNuxtの命名規則であり, 必須ではありません。
Q プラグイン内でPinia Storeを使用できますか?
A はい, ただしタイミングに注意が必要です。Piniaはプラグインより先に初期化されるため, プラグイン内でuseXxxStore()を使用できます。ただし, StoreはComposable内で管理し, プラグインは初期化のみに使用することをお勧めします。
Q クライアント側プラグインはいつ実行されますか?
A クライアント側のハイドレーション時に実行されます。.client.tsプラグインはSSRフェーズでは実行されません。ページが初めて読み込まれる際, クライアント側プラグインはハイドレーション完了後に実行されます。
Q プラグインを動的に登録できますか?
A 推奨されません。Nuxtプラグインはビルド時に解決され, ランタイムで自動実行されます。初期化を遅延させたい場合は, プラグイン内で遅延読み込み (動的インポートなど)を使用してください。
Q サードパーティSDK (Stripeなど)の読み込みが遅い場合はどうすればよいですか?
A .client.tsプラグイン内でSDKを動的に読み込み, ハイドレーションをブロックしないようにします:const Stripe = (await import('@stripe/stripe-js')).default。または<script>タグで<head>セクションに事前読み込みします。

📖 まとめ


📝 練習問題

  1. 基本問題 (難易度:⭐):ロガープラグインを作成し, コンポーネント内でuseNuxtApp().$logger.info()を使ってメッセージをログ出力してください。
  2. 応用問題 (難易度:⭐⭐):Stripeクライアントプラグインとデータベースサーバープラグインを作成し, それぞれ異なる環境で実行されることを確認してください。
  3. チャレンジ (難易度:⭐⭐⭐):アナリティクスプラグインを作成し, nuxtApp.hook('page:finish')を使ってページビューを自動トラッキングし, ロガープラグインと連携してデータを記録してください。

---|

Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%