404 Not Found

404 Not Found


nginx

ミドルウェア

Bobは, Aliceが権限チェックなしで/admin管理画面に直接アクセスできることを発見しました。Charlieにはルートガードが必要です。認証されていないユーザーが管理画面にアクセスしようとしたらログインページにリダイレクトし, 一般ユーザーが管理ルートにアクセスするのを防ぐものです。Nuxt 3のミドルウェアシステムがその解決策です。

1. 学ぶ内容


2. 管理者のある本当の話

(1) 課題:権限制御不足による重大な脆弱性

Aliceはブラウザに/adminと入力してMegaShop管理画面にアクセスし, すべての注文とユーザーデータを閲覧できました。Bobは管理者として, 自分が見る画面が一般ユーザーと全く同じであることに気づきました。Charlieはルートレベルのアクセス制御を急務として必要としていました。

(2) ルーティングミドルウェアの解決策

Nuxt 3では, ミドルウェアはルート遷移前に実行され, リダイレクトやアクセスブロックが可能です:

TYPESCRIPT
// middleware/auth.ts
export default defineNuxtRouteMiddleware((to) => {
  if (!isAuthenticated()) return navigateTo('/login')
})

(3) メリット:ルートレベルのアクセス制御

Aliceは/adminに直接アクセスできなくなり, ログインしていない場合はログインページに自動的にリダイレクトされます。Bobの管理者権限はルーティングレベルで強制されます。


3. 3つのミドルウェアパターン

(1) ミドルウェアの実行順序

100%
flowchart LR
    A[ルートナビゲーション] --> B[グローバルミドルウェア 1]
    B --> C[グローバルミドルウェア 2]
    C --> D[名前付き/インラインミドルウェア]
    D --> E[ページレンダリング]
    D -->|中断| F[リダイレクト / 中断]
    B -->|中断| F
    C -->|中断| F

(2) 3つのモデルの比較

項目 グローバルミドルウェア 名前付きミドルウェア インラインミドルウェア
ファイル名 *.global.ts *.ts ("global"なし) ファイルなし
スコープ 全ルート 特定ページ 現在のページ
定義方法 middleware/ ディレクトリ middleware/ ディレクトリ definePageMeta内
再利用性 ✅ 自動再利用 ✅ 複数ページ参照 ❌ 現在のページのみ
ユースケース グローバル認証/ログ ページ固有ガード 単純な一回限りチェック

4. グローバルミドルウェア

(1) ▶ サンプル:グローバル認証ミドルウェア

TYPESCRIPT
// middleware/auth.global.ts
export default defineNuxtRouteMiddleware((to, from) => {
  // 公開ページは認証をスキップ
  const publicPages = ['/', '/products', '/about', '/login', '/register']
  if (publicPages.includes(to.path)) return

  const token = useCookie('auth-token')
  if (!token.value) {
    return navigateTo(`/login?redirect=${to.path}`, { redirectCode: 302 })
  }
})

出力:

TEXT
// 実行成功

(2) ▶ サンプル:グローバルログミドルウェア

TYPESCRIPT
// middleware/log.global.ts
export default defineNuxtRouteMiddleware((to, from) => {
  console.log(`Navigation: ${from.path} → ${to.path}`)
  // returnなし = ナビゲーションを許可
})

出力:

TEXT
// 実行成功
⚠️ 注意: グローバルミドルウェアはファイル名のアルファベット順で実行されます。01-auth.global.ts02-log.global.tsより先に実行されます。数字プレフィックスを使用して順序を制御することをお勧めします。


5. 名前付きミドルウェア

(1) ▶ サンプル:管理者ロールミドルウェア

TYPESCRIPT
// middleware/admin.ts
export default defineNuxtRouteMiddleware((to) => {
  const user = useState('user')

  if (!user.value) {
    return navigateTo(`/login?redirect=${to.path}`)
  }

  if (user.value.role !== 'admin') {
    // 一般ユーザーが管理画面にアクセスしようとした
    throw createError({
      statusCode: 403,
      message: 'Access denied. Admin role required.'
    })
  }
})

出力:

TEXT
// 実行成功

(2) ▶ サンプル:ページで名前付きミドルウェアを参照

VUE
<!-- pages/admin/index.vue -->
<template>
  <div>
    <h1>Admin Dashboard</h1>
    <p>Welcome, Bob (Admin)</p>
  </div>
</template>

<script setup lang="ts">
definePageMeta({
  middleware: 'admin', // 名前付きミドルウェアを参照
  layout: 'sidebar'
})
</script>

出力:

TEXT
// 実行成功

(3) ▶ サンプル:複数ミドルウェアの組み合わせ

VUE
<!-- pages/admin/settings.vue -->
<template>
  <div>
    <h1>Admin Settings</h1>
  </div>
</template>

<script setup lang="ts">
definePageMeta({
  middleware: ['auth', 'admin'], // 順番に実行:auth → admin
  layout: 'sidebar'
})
</script>

出力:

TEXT
// 実行成功

6. インラインミドルウェア

(1) ▶ サンプル:ページ内でミドルウェアを定義

VUE
<!-- pages/checkout.vue -->
<template>
  <div>
    <h1>Checkout</h1>
    <p>Cart total: ${{ total }} USD</p>
  </div>
</template>

<script setup lang="ts">
definePageMeta({
  middleware: defineNuxtRouteMiddleware((to) => {
    // インライン:カートに商品があるかチェック
    const cart = useState<any[]>('cart')
    if (cart.value.length === 0) {
      return navigateTo('/cart')
    }
  })
})

const cart = useState<any[]>('cart')
const total = computed(() => cart.value.reduce((s, i) => s + i.price, 0))
</script>

出力:

TEXT
// 実行成功

(1) インラインと名前付きミドルウェアの選び方

シナリオ 選択 理由
サイト全体の認証 グローバル 全ルートをチェック
管理ルート 名前付き 複数の管理ページで共有
カートが空でないかチェック インライン 注文ページでのみ使用
VIPエリア 名前付き 複数ページで使用する可能性
A/Bテストのトラフィック分割 インライン 一回限りのロジック

7. ミドルウェアとSSR

(1) SSR二重実行メカニズム

100%
flowchart TB
    A[ユーザーが/adminにナビゲーション] --> B[サーバー:ミドルウェア実行]
    B -->|リダイレクト| C[サーバーがリダイレクトレスポンス送信]
    B -->|許可| D[サーバーがページレンダリング]
    D --> E[クライアント:ミドルウェア再実行]
    E -->|リダイレクト| F[クライアント側リダイレクト]
    E -->|許可| G[クライアントがページをハイドレーション]

(1) ▶ サンプル:クライアント専用ミドルウェア

TYPESCRIPT
// middleware/client-only.ts
export default defineNuxtRouteMiddleware((to) => {
  // サーバーではスキップ - クライアントのみチェック
  if (import.meta.server) return

  const localStorage = window.localStorage
  const preferences = JSON.parse(localStorage.getItem('user-preferences') || '{}')

  if (preferences.theme === 'dark') {
    // ブラウザAPIを使用するクライアント専用ロジック
    document.documentElement.classList.add('dark')
  }
})

出力:

TEXT
// 実行成功

(2) SSRミドルウェアの注意点

問題 原因 解決策
ミドルウェアが2回実行される SSRとクライアントで各1回実行 期待される動作;べき等性を確保
window/localStorageの使用 SSR (ブラウザ環境なし) if (import.meta.client)でチェック
CookieはSSRで利用可能 リクエストにCookieが含まれる useCookieで読み取り
SSRでのnavigateTo 302リダイレクトを返す 期待される動作
ミドルウェア内のuseState ✅ 利用可能 ログイン状態チェックに適している

8. 総合例:MegaShop権限ガードシステム

TYPESCRIPT
// middleware/01-auth.global.ts - グローバル認証チェック
export default defineNuxtRouteMiddleware((to) => {
  const publicPaths = ['/', '/products', '/products/**', '/about', '/login', '/register']
  const isPublic = publicPaths.some(path => {
    if (path.includes('')) return to.path.startsWith(path.replace('/', ''))
    return path === to.path
  })

  if (isPublic) return

  const token = useCookie('auth-token')
  if (!token.value) {
    return navigateTo(`/login?redirect=${encodeURIComponent(to.path)}`)
  }
})
TYPESCRIPT
// middleware/admin.ts - 管理者ロールチェック
export default defineNuxtRouteMiddleware((to) => {
  const user = useCookie('user-data')

  if (!user.value) {
    return navigateTo('/login')
  }

  if ((user.value as any).role !== 'admin') {
    throw createError({ statusCode: 403, message: 'Admin access required' })
  }
})
VUE
<!-- pages/admin/products/index.vue -->
<template>
  <div>
    <h1>Product Management</h1>
    <p>Manage 1 million products</p>
    <!-- 管理者商品CRUDインターフェース -->
  </div>
</template>

<script setup lang="ts">
definePageMeta({
  middleware: ['admin'],
  layout: 'sidebar'
})
</script>

❓ よくある質問

Q ミドルウェアの実行順序はどう制御しますか?
A グローバルミドルウェアはファイル名のアルファベット順にソートされます。数字プレフィックス (01-xxx.global.ts)の使用をお勧めします。名前付きミドルウェアはdefinePageMeta内の配列で指定された順序で実行されます。
Q ミドルウェア内で非同期処理は可能ですか?
A defineNuxtRouteMiddleware自体はasyncをサポートしていません。ただし, ミドルウェア内で同期的なAPI (useCookieuseStateなど)を呼び出すことは可能です。非同期検証が必要な場合は, プラグインまたはページのsetupメソッドで処理することをお勧めします。
Q なぜミドルウェアはSSRで1回, クライアント側で1回実行されるのですか?
A SSRでは, ミドルウェアがルート権限をチェックしてページをレンダリングするかどうかを決定します。クライアント側のハイドレーション時にも再度実行され, 一貫性が確保されます。両方の実行ロジックはべき等である必要があります。
Q グローバルミドルウェアはパフォーマンスに影響しますか?
A すべてのグローバルミドルウェアはルートナビゲーションのたびに実行されます。ミドルウェアのロジックは軽量に保ち, 重い計算やAPI呼び出しをグローバルミドルウェア内で行うのは避けてください。
Q ミドルウェア内でcreateErrorがスローされた場合はどうなりますか?
A SSRでは, 対応するステータスコードのHTMLエラーページが返されます (例:403ページ)。クライアント側では, Nuxtエラーページが表示されます。error.vueでカスタマイズ可能です。
Q ミドルウェア間でデータを渡すにはどうすればよいですか?
A useStateまたはNuxtApppayloadプロパティを使用します。const nuxtApp = useNuxtApp()nuxtApp.payload.xxx = data。次のミドルウェアまたはページからアクセスできます。

📖 まとめ


📝 練習問題

  1. 基本問題 (難易度:⭐):guest.tsという名前のミドルウェアを作成し, 未認証ユーザーのみがページにアクセスできるようにします (ログイン済みユーザーはホームページにリダイレクト)。これを/loginページに適用してください。
  2. 応用問題 (難易度:⭐⭐):完全な認可システムを実装:グローバル認証チェックでログインを確認 + 「admin」ユーザーのロールチェック。/admin/**ルートを保護します。
  3. チャレンジ (難易度:⭐⭐⭐):ミドルウェア間のデータ渡しを実装—authミドルウェアでユーザー情報を取得し, NuxtAppのpayloadに格納。その後adminミドルウェアで直接読み取り, 重複リクエストを回避します。

---|

Web-Tutorial.com

Web-Tutorial 技術チーム

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

100%