プラグインシステム
CharlieはMegaShopにStripe決済SDKを統合する必要があり, グローバルロギングサービスも欲しています。Stripeはクライアント側でのみ読み込まれ, ログはサーバー側で記録されます。従来は複数箇所で手動インポートと初期化が必要でしたが, Nuxt 3のプラグインシステムは全プロセスを自動化します。一度登録すれば, どこでも利用可能です。
1. 学ぶ内容
- "plugins/"ディレクトリの自動登録と実行タイミング
- defineNuxtPlugin:
provideとinjectの使用 - サーバー側/クライアント側プラグイン:.server.tsと.client.tsファイル拡張子による区別
- プラグイン実行順序の制御
- MegaShop決済SDKとログプラグインの実践ガイド
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) プラグイン登録と実行タイミング
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>セクションに事前読み込みします。📖 まとめ
plugins/ディレクトリは自動登録され, ファイルはファイル名のアルファベット順で実行されますdefineNuxtPlugin+provideでグローバルサービスを注入し, コンポーネントはuseNuxtApp().$xxxでアクセス- .server.tsと.client.tsのファイル拡張子で実行環境を区別し, ブラウザとNode APIの競合を回避
- 数字プレフィックスで実行順序を制御し, 依存関係を正しく処理
- MegaShopはプラグインでStripe決済, Logger, Notificationなどのサードパーティサービスを統合
📝 練習問題
- 基本問題 (難易度:⭐):ロガープラグインを作成し, コンポーネント内で
useNuxtApp().$logger.info()を使ってメッセージをログ出力してください。 - 応用問題 (難易度:⭐⭐):Stripeクライアントプラグインとデータベースサーバープラグインを作成し, それぞれ異なる環境で実行されることを確認してください。
- チャレンジ (難易度:⭐⭐⭐):アナリティクスプラグインを作成し,
nuxtApp.hook('page:finish')を使ってページビューを自動トラッキングし, ロガープラグインと連携してデータを記録してください。
---|



