Vue Router ナビゲーションガード認証とルート保護

2025-07-31

はじめに

Vue.jsのルーティングシステムであるVue Routerには、ナビゲーションを制御する強力な機能「ナビゲーションガード」が備わっています。この記事では、ナビゲーションガードの基本から実践的な活用方法まで詳細な解説を行います。

ナビゲーションガードとは?

ナビゲーションガードは、Vue Routerが提供するナビゲーション(ルート遷移)をフックして制御する仕組みです。主に以下のような場面で使用します:

  • ユーザー認証の確認
  • ルートアクセス権限のチェック
  • 未保存データの確認
  • ページ遷移前のデータ取得
  • 分析トラッキングの実施

ナビゲーションガードの種類

Vue Routerには3つの主要なナビゲーションガードがあります:

  1. グローバルガード – すべてのナビゲーションで実行
  2. ルート単位ガード – 特定のルートに対して実行
  3. コンポーネント内ガード – コンポーネント内で実行

1. グローバルガード

グローバルガードはルーターインスタンスに直接登録され、すべてのナビゲーションで実行されます。

router.beforeEach – グローバル前置ガード

const router = createRouter({ ... })

router.beforeEach((to, from, next) => {
  // to: 遷移先のルートオブジェクト
  // from: 遷移元のルートオブジェクト
  // next: ナビゲーションを解決する関数
  if (to.meta.requiresAuth && !isAuthenticated()) {
    next('/login') // ログインページにリダイレクト
  } else {
    next() // ナビゲーションを許可
  }
})

router.beforeResolve – グローバル解決ガード

beforeEachに似ていますが、ナビゲーションが解決される直前、コンポーネントガードと非同期コンポーネントが解決された後に呼び出されます。

router.beforeResolve(async to => {
  if (to.meta.requiresFetch) {
    await fetchData(to.params.id)
  }
})

router.afterEach – グローバル後置ガード

ナビゲーションが完了した後に実行されます。next関数は受け取りません。

router.afterEach((to, from) => {
  sendToAnalytics(to.fullPath)
})

2. ルート単位ガード

特定のルートに対して直接定義できるガードです。

const routes = [
  {
    path: '/admin',
    component: AdminPanel,
    beforeEnter: (to, from, next) => {
      if (!isAdmin()) {
        next('/access-denied')
      } else {
        next()
      }
    }
  }
]

3. コンポーネント内ガード

ルートコンポーネント内で直接定義できるガードです。

beforeRouteEnter

export default {
  beforeRouteEnter(to, from, next) {
    // コンポーネントインスタンスはまだ作成されていない
    next(vm => {
      // vmを通じてコンポーネントインスタンスにアクセス
      console.log(vm.someProperty)
    })
  }
}

beforeRouteUpdate

export default {
  beforeRouteUpdate(to, from, next) {
    // 同じコンポーネントでルートが変更された時
    // 例: /users/1 → /users/2
    this.userData = fetchUser(to.params.id)
    next()
  }
}

beforeRouteLeave

export default {
  beforeRouteLeave(to, from, next) {
    if (this.unsavedChanges) {
      if (confirm('変更が保存されていません。離れますか?')) {
        next()
      } else {
        next(false) // ナビゲーションを中止
      }
    } else {
      next()
    }
  }
}

実践的な使用例

認証システムの実装

// 認証状態をチェックする関数
const isAuthenticated = () => {
  return localStorage.getItem('authToken') !== null
}

// 管理者権限をチェックする関数
const isAdmin = () => {
  const user = JSON.parse(localStorage.getItem('user'))
  return user && user.role === 'admin'
}

router.beforeEach((to, from, next) => {
  if (to.meta.requiresAuth && !isAuthenticated()) {
    // 認証が必要なページかつ未認証の場合
    next({
      path: '/login',
      query: { redirect: to.fullPath } // ログイン後にリダイレクト
    })
  } else if (to.meta.adminOnly && !isAdmin()) {
    // 管理者限定ページかつ管理者でない場合
    next('/forbidden')
  } else {
    next()
  }
})

ルートメタフィールドの活用

const routes = [
  {
    path: '/',
    component: Home,
    meta: { requiresAuth: false }
  },
  {
    path: '/dashboard',
    component: Dashboard,
    meta: { requiresAuth: true }
  },
  {
    path: '/admin',
    component: Admin,
    meta: { requiresAuth: true, adminOnly: true }
  }
]

ページタイトルの動的設定

router.beforeEach((to, from, next) => {
  document.title = to.meta.title || 'デフォルトタイトル'
  next()
})

ナビゲーションガードの実行順序

ナビゲーションガードは以下の順序で実行されます:

  1. ナビゲーションがトリガーされる
  2. 非アクティブ化されるコンポーネントでbeforeRouteLeaveを呼び出し
  3. グローバルbeforeEachガードを呼び出し
  4. 再利用されるコンポーネントでbeforeRouteUpdateを呼び出し
  5. ルート設定内のbeforeEnterを呼び出し
  6. 非同期ルートコンポーネントを解決
  7. アクティブ化されるコンポーネントでbeforeRouteEnterを呼び出し
  8. グローバルbeforeResolveガードを呼び出し
  9. ナビゲーションが確定
  10. グローバルafterEachフックを呼び出し
  11. DOM更新がトリガーされる
  12. beforeRouteEnterガードで渡されたnextコールバックを呼び出し

よくあるエラーとデバッグ方法

無限リダイレクトループ

router.beforeEach((to, from, next) => {
  if (!isAuthenticated() && to.path !== '/login') {
    next('/login') // ログインページへ
  } else {
    next() // 許可
  }
})

このコードでは、/loginページ自体にrequiresAuthメタフィールドが設定されていると無限ループに陥る可能性があります。

解決策:

router.beforeEach((to, from, next) => {
  if (to.meta.requiresAuth && !isAuthenticated() && to.path !== '/login') {
    next('/login')
  } else {
    next()
  }
})

next()の呼び忘れ

ガード関数でnext()を呼び忘れると、ナビゲーションがハングします。

router.beforeEach((to, from, next) => {
  if (someCondition) {
    next('/other-path')
  }
  // next()が呼ばれていない!
})

正しい実装:

router.beforeEach((to, from, next) => {
  if (someCondition) {
    next('/other-path')
  } else {
    next()
  }
})

パフォーマンス最適化のヒント

  1. ガード内の処理を最小化: ガード内で重い処理を行わない
  2. メモ化: 繰り返し行われる認証チェックをキャッシュ
  3. 非同期処理の適切な管理: ガード内でAPI呼び出しが必要な場合はローディング状態を表示
  4. 不要なガードを削除: 本当に必要な場合のみガードを使用

テスト戦略

ナビゲーションガードのテスト例(Jestを使用):

import { createRouter, createWebHistory } from 'vue-router'
import { routes } from '@/router'
import { isAuthenticated } from '@/auth'

jest.mock('@/auth')

describe('Navigation Guards', () => {
  let router

  beforeEach(() => {
    router = createRouter({
      history: createWebHistory(),
      routes
    })
  })

  it('should redirect to login when accessing protected route without auth', async () => {
    isAuthenticated.mockReturnValue(false)
    await router.push('/protected')
    expect(router.currentRoute.value.path).toBe('/login')
  })

  it('should allow access to protected route with auth', async () => {
    isAuthenticated.mockReturnValue(true)
    await router.push('/protected')
    expect(router.currentRoute.value.path).toBe('/protected')
  })
})

まとめ

Vue Routerのナビゲーションガードは、アプリケーションのセキュリティとユーザー体験を向上させる強力なツールです。適切に実装することで:

  • 認証状態に基づいてルートを保護
  • 権限に応じたアクセス制御
  • 未保存データの損失防止
  • ナビゲーション前のデータ事前取得
  • 分析トラッキングの実施

が可能になります。

ナビゲーションガードを効果的に使用するには、その実行順序と各ガードの特性を理解することが重要です。また、無限リダイレクトループなどの一般的な落とし穴を避けるため、慎重に実装する必要があります。

さらに学ぶために

ナビゲーションガードをマスターすることで、より安全で堅牢なVue.jsアプリケーションを構築できるようになります。実際のプロジェクトでこれらのテクニックを試しながら、理解を深めていきましょう。