1. 結論(この記事で得られること)
Nuxt3では、Nuxt2から plugin の配置ルールと実行順序が大きく変わりました。この記事では以下を習得できます:
- Nuxt2との差分を正確に理解し、既存資産を安全に移行する方法
- 「.server」/「.client」 接尾辞の正しい使い分けと、SSR/CSRでのplugin分離設計
- 実行順序を保証する命名規則(番号プレフィックス)の実務パターン
- AI を活用した問題切り分けで半日かかる調査を30分に短縮する手法
- 型安全に provide/inject を運用するTypeScript設計とテスト戦略
2. 前提(環境・読者層)
想定環境:
- Nuxt 3.x(執筆時点 3.10以降を推奨)
- Node.js 18.x 以上
- TypeScript での開発を想定(JS の場合も応用可能)
想定読者:
- Nuxt2 経験者で Nuxt3 への移行を検討している方
- plugin の実行タイミングや順序で困った経験のある方
- チーム開発で plugin 設計の標準化が必要な方
事前知識:
- Nuxt の基本的なディレクトリ構造(「pages/」, 「composables/」 など)
- Vue3 の Composition API と provide/inject の基礎
- SSR/CSR の違いを概念的に理解している
3. Before:よくあるつまずきポイント
3-1. Nuxt2 の書き方がそのまま動かない
Nuxt2 では 「nuxt.config.js」 に以下のように記述していました:
// Nuxt2 での記述(Nuxt3では動かない)
export default {
plugins: [
'~/plugins/axios',
{ src: '~/plugins/ga', mode: 'client' },
]
}
Nuxt3 では 「plugins/」 ディレクトリに配置するだけで自動登録されるため、config への記述は不要です。しかし、この仕様変更を知らずに config に書いてしまい「plugin が二重実行される」トラブルに遭遇するケースが頻発します。
3-2. 実行順序が保証されない
Nuxt3 では plugin はアルファベット順に実行されますが、暗黙的な依存関係がある場合に問題が起きます:
plugins/
auth.ts // 認証初期化(先に実行したい)
api-client.ts // 認証トークンを前提とするAPI client
この配置だと 「api-client.ts」 が先に実行され、認証情報がない状態で初期化されてしまいます。昔の私はこれで「開発環境では動くのに本番で 401 エラー」という地獄を味わいました。
3-3. SSR/CSR の分離が曖昧
Nuxt2 の 「mode: 'client'」 が通用せず、以下のような問題が起きます:
// window オブジェクトを使うコード(SSRで実行されるとエラー)
export default defineNuxtPlugin(() => {
const tracker = new window.analytics.Tracker() // ReferenceError: window is not defined
})
「ローカルでは動くのにデプロイしたら500エラー」という典型的な SSR トラブルです。
4. After:基本的な解決パターン
4-1. ファイル名接尾辞で実行環境を制御
Nuxt3 では ファイル名の接尾辞で SSR/CSR を分離します:
plugins/
01.auth.ts // SSR/CSR 両方で実行
02.api-client.ts // 両方で実行
03.analytics.client.ts // クライアントのみ
04.prerender-meta.server.ts // サーバーのみ
命名規則:
- 「.client.ts」 → ブラウザのみで実行
- 「.server.ts」 → サーバー(SSR/SSG)のみで実行
- 接尾辞なし → 両方で実行
4-2. 番号プレフィックスで実行順序を保証
// plugins/01.auth.ts
export default defineNuxtPlugin(() => {
const authToken = useCookie('auth_token')
return {
provide: {
auth: {
token: authToken.value,
isAuthenticated: !!authToken.value
}
}
}
})
// plugins/02.api-client.ts
export default defineNuxtPlugin((nuxtApp) => {
const auth = nuxtApp.$auth // 01.auth.ts で provide された値を利用
const api = $fetch.create({
baseURL: '/api',
headers: {
Authorization: auth.token ? `Bearer ${auth.token}` : ''
}
})
return {
provide: { api }
}
})
ポイント:
- 「01」, 「02」 のように2桁の番号をプレフィックスとして付与(9個以上になっても順序が崩れない)
- 依存関係が明確になり、コードレビューでも意図が伝わる
4-3. TypeScript 型定義の追加
// types/nuxt.d.ts
declare module '#app' {
interface NuxtApp {
$auth: {
token: string | null
isAuthenticated: boolean
}
$api: typeof $fetch
}
}
declare module 'vue' {
interface ComponentCustomProperties {
$auth: {
token: string | null
isAuthenticated: boolean
}
$api: typeof $fetch
}
}
export {}
これで 「nuxtApp.$auth」 や template 内の 「$auth」 が型安全になります。