1. 結論(この記事で得られること)
この記事では、Nuxt3のmiddleware実装をNuxt2と比較しながら、実務で安全に移行・実装する方法をお伝えします。
具体的に得られること:
- Nuxt2とNuxt3のmiddleware実装の違いとその理由
- 型安全性を活かしたNuxt3らしい実装パターン
- 既存コードの移行チェックリスト
- AIを活用した問題切り分けと効率的なデバッグ手法
- 実務で起こる失敗パターンとその対処法
私も初めてNuxt3のmiddlewareに触れたとき、「context」が消えて「え、「redirect」どうやるの?」と10分ぐらい固まった経験があります。この記事を読めば、そういう時間を節約できます。
2. 前提(環境・読者層)
想定環境:
- Nuxt 3.x(3.10以降推奨)
- TypeScript有効
- Node.js 18以上
想定読者:
- Nuxt2からの移行を検討している方
- Nuxt3で認証・権限制御を実装する方
- middleware周りでハマって困っている方
この記事で扱わないこと:
- Nuxt2の基本的な使い方
- Vue3の基礎文法
3. Before:よくあるつまずきポイント
3-1. Nuxt2のmiddleware(従来の書き方)
Nuxt2では「context」オブジェクトに全てが詰まっていました。
// middleware/auth.js (Nuxt2)
export default function ({ store, redirect, route }) {
if (!store.state.user.loggedIn) {
return redirect('/login?redirect=' + route.path)
}
}
// nuxt.config.js (Nuxt2)
export default {
router: {
middleware: ['auth']
}
}
Nuxt2の特徴:
- 「context」から必要なものを分割代入
- 「redirect」はcontext内の関数
- グローバル指定は「nuxt.config.js」
3-2. Nuxt3で同じコードを書くと起こる問題
// ❌ これはNuxt3で動かない
export default function ({ store, redirect, route }) {
// Error: context is undefined
}
よくあるエラーメッセージ:
- 「navigateTo is not defined」
- 「useRouter is not a function in middleware」
- 「Cannot read properties of undefined (reading 'path')」
私が最初にハマったのは「グローバルmiddlewareの指定場所が分からない」でした。「nuxt.config.ts」を探し回ったんですが、実はファイル名規約で解決する仕様になっていたんです。
3-3. なぜ変わったのか(設計思想の違い)
Nuxt3では以下の思想で再設計されています:
コンテキスト
Nuxt2: 巨大なcontextオブジェクト
Nuxt3: Composables (useRouter等)
型安全性
Nuxt2: 弱い(JSベース)
Nuxt3: 強い(TS first)
実行順序
Nuxt2: 曖昧
Nuxt3: 明確(ファイル名・定義順)
SSR/CSR
Nuxt2: 混在して分かりづらい
Nuxt3: 明示的に分離可能
この変更は「巨大なcontextに何でも詰め込む設計からの脱却」です。実際、レビューでNuxt2のmiddlewareを見ると「これ何に依存してるか分からん」ってなりがちでした。
4. After:基本的な解決パターン
4-1. Nuxt3の基本構文
// middleware/auth.ts (Nuxt3)
export default defineNuxtRouteMiddleware((to, from) => {
const user = useState('user')
if (!user.value?.loggedIn) {
return navigateTo({
path: '/login',
query: { redirect: to.path }
})
}
})
重要な変更点:
- 「defineNuxtRouteMiddleware」でラップ
- 引数は「(to, from)」 - Vue Routerと同じ
- 「navigateTo」はグローバルに利用可能
- 「useState」で状態を取得
4-2. グローバルmiddlewareの指定方法
// middleware/auth.global.ts
// ↑ ファイル名に `.global.ts` をつけるだけ
export default defineNuxtRouteMiddleware((to, from) => {
// 全ページで実行される
})
4-3. ページ単位での指定
<!-- pages/dashboard.vue -->
<script setup lang="ts">
definePageMeta({
middleware: ['auth', 'role-check']
})
</script>
4-4. 条件分岐とリダイレクト
// middleware/role-check.ts
export default defineNuxtRouteMiddleware((to, from) => {
const user = useState<User>('user')
// 特定のパスでのみ実行
if (to.path.startsWith('/admin')) {
if (user.value?.role !== 'admin') {
return navigateTo('/forbidden')
}
}
// 何も返さなければ通過
})
ポイント:
- 「return」しなければ次に進む
- 「navigateTo」の戻り値をそのまま「return」
- 型推論が効くのでtypo防止になる