Vue Routerとは

Vue Routerは、Vue.js公式のルーティングライブラリです。SPA(Single Page Application)では画面遷移ごとにサーバーへリクエストを送らず、JavaScriptでURLとコンポーネントの対応関係を管理します。Vue Routerがこの対応関係の制御を一手に担っています。

Vue 3に対応するVue Router 4が長らく標準でしたが、2026年1月にVue Router 5がリリースされました(出典: GitHub vuejs/router)。Vue Router 5はファイルベースルーティングツール「Unplugin Vue Router」の機能をコアに統合した版で、unplugin-vue-routerを使っていないプロジェクトであれば破壊的変更なくアップグレードできます。本記事のコード例はVue Router 4の構文で記載しており、Vue Router 5でもそのまま動作します。

主な機能は以下の通りです。

  • URLパスとコンポーネントのマッピング(ルート定義)
  • 動的パラメータ・クエリ文字列の受け渡し
  • ネストされたルートによる階層的なレイアウト構築
  • ナビゲーションガードによるアクセス制御
  • HTML5 History APIを活用したクリーンなURL
  • ルート単位のコード分割(遅延読み込み)

Vue Routerのインストールと初期設定

パッケージのインストール

Vue Router 4のインストールは、npm・yarn・pnpmのいずれでも可能です。

# npm
npm install vue-router@4

# yarn
yarn add vue-router@4

# pnpm
pnpm add vue-router@4

新規プロジェクトの場合は npm create vue@latest で作成時にVue Routerの導入を選択する方法もあります。

ルーターインスタンスの作成

src/router/index.ts にルーター設定ファイルを作成します。

// src/router/index.ts
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
import AboutView from '../views/AboutView.vue'

const routes = [
  {
    path: '/',
    name: 'home',
    component: HomeView,
  },
  {
    path: '/about',
    name: 'about',
    component: AboutView,
  },
]

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes,
})

export default router

createRouter にはルート定義の配列と履歴モード(後述)を渡します。createWebHistory はHTML5 History APIを使ったクリーンなURL形式を有効にする関数です。

アプリケーションへの登録

main.ts でルーターをプラグインとして登録します。

// src/main.ts
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'

const app = createApp(App)
app.use(router)
app.mount('#app')

App.vueにrouter-viewを配置

ルートに対応するコンポーネントは <RouterView> の位置に描画されます。<RouterLink> はページ遷移のリンクを生成するコンポーネントです。

<!-- src/App.vue -->
<script setup lang="ts">
import { RouterLink, RouterView } from 'vue-router'
</script>

<template>
  <nav>
    <RouterLink to="/">ホーム</RouterLink>
    <RouterLink to="/about">概要</RouterLink>
  </nav>
  <RouterView />
</template>

通常のHTMLの <a> タグと異なり、<RouterLink> はページ全体をリロードせずにURLを切り替えます。現在のルートと一致するリンクには自動でCSSクラス router-link-active が付与されるため、ナビゲーションのハイライト表示にも便利です。

ルート定義のパターン

静的ルート

パスとコンポーネントを1対1で対応させる最もシンプルな形式です。

const routes = [
  { path: '/', name: 'home', component: HomeView },
  { path: '/about', name: 'about', component: AboutView },
  { path: '/contact', name: 'contact', component: ContactView },
]

動的ルート(params)

URLの一部をパラメータとして受け取るには、パスにコロン付きのセグメントを定義します。Vue Router paramsを使うことで、ユーザーIDや記事スラッグなど可変値を含むURLを扱えます。

const routes = [
  {
    path: '/users/:id',
    name: 'user-detail',
    component: UserDetail,
  },
  {
    path: '/posts/:year/:slug',
    name: 'post',
    component: PostView,
  },
]

/users/42 にアクセスすると route.params.id'42' が格納されます。/posts/2026/vue-router-guide の場合は { year: '2026', slug: 'vue-router-guide' } が取得できます。

ネストルート(子ルート)

親コンポーネント内にさらに <RouterView> を配置し、子ルートを描画する構成です。children プロパティで階層構造を表現します。

const routes = [
  {
    path: '/dashboard',
    component: DashboardLayout,
    children: [
      { path: '', name: 'dashboard-home', component: DashboardHome },
      { path: 'settings', name: 'dashboard-settings', component: DashboardSettings },
      { path: 'profile', name: 'dashboard-profile', component: DashboardProfile },
    ],
  },
]
<!-- DashboardLayout.vue -->
<template>
  <div class="dashboard">
    <aside>サイドバー</aside>
    <main>
      <RouterView />
    </main>
  </div>
</template>

/dashboard へアクセスすると DashboardHome が描画され、/dashboard/settings なら DashboardSettings が描画されます。親コンポーネントのレイアウト(サイドバーなど)は共通で維持されます。

名前付きルート

ルートに name を設定すると、パス文字列ではなく名前で遷移先を指定できます。URLの変更に強く、タイプミスも減ります。

<RouterLink :to="{ name: 'user-detail', params: { id: 42 } }">
  ユーザー詳細
</RouterLink>

画面遷移の方法

Vue Routerには宣言的ナビゲーション(テンプレート)とプログラマティックナビゲーション(JavaScript)の2種類があります。

RouterLink(宣言的)

テンプレート内でリンクを定義する方法です。Vue Router linkとして最も基本的な遷移手段になります。

<template>
  <!-- 文字列パス -->
  <RouterLink to="/about">概要ページ</RouterLink>

  <!-- オブジェクト形式 -->
  <RouterLink :to="{ name: 'user-detail', params: { id: userId } }">
    プロフィール
  </RouterLink>

  <!-- クエリ付き -->
  <RouterLink :to="{ path: '/search', query: { q: 'vue' } }">
    検索
  </RouterLink>
</template>

router.push()(プログラマティック)

Vue Router pushは、ボタンクリックやフォーム送信後など、ロジックの中で遷移を実行する場合に使います。ブラウザの履歴スタックに新しいエントリを追加するため、戻るボタンで前の画面に戻れます。

<script setup lang="ts">
import { useRouter } from 'vue-router'

const router = useRouter()

function goToUser(id: number) {
  router.push({ name: 'user-detail', params: { id } })
}

function searchKeyword(keyword: string) {
  router.push({ path: '/search', query: { q: keyword } })
}
</script>

router.push() はPromiseを返すため、遷移の完了を await で待つことも可能です。

router.replace()

router.push() と同様に遷移しますが、履歴スタックに追加しません。ログイン後のリダイレクトなど、戻るボタンで前の画面に戻らせたくない場合に適しています。

// 方法1
router.replace({ path: '/home' })

// 方法2(pushのオプションとして指定)
router.push({ path: '/home', replace: true })

router.go()

ブラウザの履歴を前後に移動します。window.history.go() と同等の動作です。

router.go(-1)  // 1つ前に戻る
router.go(1)   // 1つ先に進む

遷移方法の選び方

メソッド履歴への追加主な用途
<RouterLink>ありテンプレート内のリンク
router.push()ありロジック内での遷移(フォーム送信後など)
router.replace()なし(上書き)リダイレクト、ログイン後の遷移
router.go(n)履歴の前後移動

ルートパラメータとクエリ文字列

useRoute()によるパラメータ取得

Composition APIでは useRoute() で現在のルート情報にアクセスします。

<script setup lang="ts">
import { useRoute } from 'vue-router'

const route = useRoute()

// /users/42 の場合
console.log(route.params.id) // '42'

// /search?q=vue&page=2 の場合
console.log(route.query.q)    // 'vue'
console.log(route.query.page) // '2'
</script>

paramsとqueryの違い

項目paramsquery
URLでの表現/users/42/search?q=vue
ルート定義path: '/users/:id'定義不要
値の型stringstring | string[]
用途リソースの識別(ID、スラッグ)フィルター条件、ページ番号

propsによるパラメータ受け渡し

Vue Router propsを使うと、コンポーネントをルーターに密結合させずにパラメータを受け取れます。テストやコンポーネントの再利用が容易になるメリットがあります。

// ルート定義
{
  path: '/users/:id',
  name: 'user-detail',
  component: UserDetail,
  props: true, // route.params をそのまま props として渡す
}
<!-- UserDetail.vue -->
<script setup lang="ts">
const props = defineProps<{
  id: string
}>()
</script>

<template>
  <div>ユーザーID: {{ id }}</div>
</template>

props には3つのモードがあります。

  • booleanモードprops: true): route.params がそのままpropsになります
  • オブジェクトモードprops: { fixed: true }): 静的な値をpropsとして渡します
  • 関数モードprops: route => ({ q: route.query.q })): 任意のロジックでpropsを生成します

ナビゲーションガードによるアクセス制御

ナビゲーションガードは、画面遷移の前後にフック処理を挟む仕組みです。認証チェックやデータの事前取得、未保存データの離脱防止などに活用されます。Vue Router beforeEachをはじめ、複数のガードが用意されています。

グローバルガード: beforeEach

すべてのルート遷移の前に実行されます。認証状態の確認に最も多く使われるガードです。

// src/router/index.ts
router.beforeEach((to, from) => {
  const isAuthenticated = !!localStorage.getItem('token')

  if (to.meta.requiresAuth && !isAuthenticated) {
    return { name: 'login', query: { redirect: to.fullPath } }
  }
})

ルート定義側では meta フィールドで認証が必要なページを指定します。

const routes = [
  {
    path: '/mypage',
    name: 'mypage',
    component: MyPage,
    meta: { requiresAuth: true },
  },
  {
    path: '/login',
    name: 'login',
    component: LoginView,
  },
]

beforeEach の戻り値で遷移を制御します。

  • undefined または true: 遷移を許可
  • false: 遷移をキャンセル
  • ルートオブジェクト: 指定先にリダイレクト

グローバルガード: beforeResolve

すべてのコンポーネント内ガードと非同期コンポーネントの解決後、遷移が確定する直前に実行されます。API呼び出しやパーミッションチェックの最終確認に適しています。

router.beforeResolve(async (to) => {
  if (to.meta.requiresPermission) {
    try {
      await checkPermission(to.meta.requiredRole as string)
    } catch {
      return { name: 'forbidden' }
    }
  }
})

ルート単位ガード: beforeEnter

特定のルートにのみ適用されるガードです。異なるルートからの遷移時のみ発火し、同じルート内でのパラメータ変更では実行されません。

const routes = [
  {
    path: '/admin',
    component: AdminPanel,
    beforeEnter: (to, from) => {
      const userRole = getUserRole()
      if (userRole !== 'admin') {
        return { name: 'home' }
      }
    },
  },
]

配列形式で複数のガード関数を指定することもできます。

function validateId(to) {
  if (isNaN(Number(to.params.id))) return { name: 'not-found' }
}
function logAccess(to) {
  console.log(`Accessed: ${to.fullPath}`)
}

const routes = [
  {
    path: '/items/:id',
    component: ItemDetail,
    beforeEnter: [validateId, logAccess],
  },
]

コンポーネント内ガード

Composition APIでは onBeforeRouteUpdateonBeforeRouteLeave が使えます。

<script setup lang="ts">
import { ref } from 'vue'
import { onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router'

const hasUnsavedChanges = ref(false)

// 同じルートでパラメータが変わった時
onBeforeRouteUpdate((to, from) => {
  console.log(`パラメータ変更: ${from.params.id}${to.params.id}`)
})

// ページから離れる時
onBeforeRouteLeave((to, from) => {
  if (hasUnsavedChanges.value) {
    const confirmed = window.confirm('未保存の変更があります。離れますか?')
    if (!confirmed) return false
  }
})
</script>

ガードの実行順序

ナビゲーション発生時、ガードは以下の順序で実行されます。

  1. 離脱元コンポーネントの beforeRouteLeave
  2. グローバル beforeEach
  3. 再利用コンポーネントの beforeRouteUpdate
  4. ルート定義の beforeEnter
  5. 非同期コンポーネントの解決
  6. 遷移先コンポーネントの beforeRouteEnter(Options APIのみ)
  7. グローバル beforeResolve
  8. 遷移確定
  9. グローバル afterEach

いずれかのガードが false を返すかリダイレクトを返した時点で、遷移はその場で中断されます。

ミドルウェアチェーンパターン

複数のガード条件を組み合わせて再利用可能なミドルウェアとして管理するパターンです。大規模アプリケーションで認証・認可・ログなどの横断的関心事を整理する際に有効です。

// src/router/middleware/auth.ts
export function authMiddleware(to, from) {
  if (!localStorage.getItem('token')) {
    return { name: 'login', query: { redirect: to.fullPath } }
  }
}

// src/router/middleware/role.ts
export function roleMiddleware(requiredRole: string) {
  return (to, from) => {
    const userRole = getUserRole()
    if (userRole !== requiredRole) {
      return { name: 'forbidden' }
    }
  }
}
// src/router/index.ts
import { authMiddleware } from './middleware/auth'
import { roleMiddleware } from './middleware/role'

const routes = [
  {
    path: '/admin/users',
    component: AdminUsers,
    beforeEnter: [authMiddleware, roleMiddleware('admin')],
  },
]

ルート定義の beforeEnter を配列にすることで、ミドルウェアを柔軟に組み合わせられます。

履歴モードの比較

Vue Routerには3種類の履歴モードがあり、createRouter で指定します。

createWebHistory(HTML5モード)

import { createWebHistory } from 'vue-router'

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

URL例: https://example.com/users/42

本番環境で最も推奨されるモードです。クリーンなURLが生成され、SEOにも有利です。ただし、サーバー側でフォールバック設定が必要です。たとえばNginxの場合は以下の設定を追加します。

location / {
  try_files $uri $uri/ /index.html;
}

この設定がないと、直接URLにアクセスした際やページをリロードした際に404エラーが返されます。

createWebHashHistory(ハッシュモード)

import { createWebHashHistory } from 'vue-router'

const router = createRouter({
  history: createWebHashHistory(),
  routes,
})

URL例: https://example.com/#/users/42

URLにハッシュ記号 # が含まれます。サーバー側の設定は不要ですが、SEOには不利です。サーバー設定を変更できない環境(GitHub Pagesなど)での利用に向いています。

createMemoryHistory(メモリモード)

import { createMemoryHistory } from 'vue-router'

const router = createRouter({
  history: createMemoryHistory(),
  routes,
})

URLはブラウザのアドレスバーに反映されません。SSR(サーバーサイドレンダリング)やテスト環境で使用されるモードです。ブラウザの戻る・進む操作には対応していません。

モード比較表

モードURL形式サーバー設定SEO主な用途
createWebHistory/users/42必要有利本番Webアプリ
createWebHashHistory/#/users/42不要不利サーバー設定不可な環境
createMemoryHistoryURLに反映なし不要対象外SSR・テスト

遅延読み込みとコード分割

SPAではすべてのページコンポーネントを初回読み込み時にダウンロードするとパフォーマンスが低下します。Vue Routerは動的インポート構文を使ったルート単位のコード分割に対応しています。

基本的な遅延読み込み

静的インポートを動的インポートに置き換えるだけで実現できます。

// 静的インポート(初回にすべてバンドルされる)
// import UserList from '../views/UserList.vue'

// 動的インポート(アクセス時にロードされる)
const UserList = () => import('../views/UserList.vue')

const routes = [
  {
    path: '/users',
    name: 'user-list',
    component: UserList,
  },
  {
    path: '/settings',
    name: 'settings',
    // ルート定義内に直接記述も可能
    component: () => import('../views/SettingsView.vue'),
  },
]

Viteやwebpackなどのバンドラーがこのインポートを検出し、自動的にチャンク(分割されたJSファイル)を生成します。ユーザーがそのルートに初めてアクセスした時点で対応するチャンクがダウンロードされます。

Viteでのチャンクグループ化

関連性の高いルートを同一チャンクにまとめたい場合は、Viteの設定で manualChunks を指定します。

// vite.config.ts
export default defineConfig({
  build: {
    rollupOptions: {
      output: {
        manualChunks: {
          'admin-group': [
            './src/views/AdminDashboard.vue',
            './src/views/AdminUsers.vue',
            './src/views/AdminSettings.vue',
          ],
        },
      },
    },
  },
})

この設定により、管理画面のコンポーネントが1つのチャンクにまとまり、管理画面内での遷移ではネットワークリクエストが発生しません。

TypeScriptとの連携

RouteRecordRawによる型安全なルート定義

Vue Router 4はTypeScriptを標準でサポートしています。ルート定義に RouteRecordRaw 型を適用すると、定義ミスをコンパイル時に検出できます。

import type { RouteRecordRaw } from 'vue-router'

const routes: RouteRecordRaw[] = [
  {
    path: '/',
    name: 'home',
    component: () => import('../views/HomeView.vue'),
  },
  {
    path: '/users/:id',
    name: 'user-detail',
    component: () => import('../views/UserDetail.vue'),
    props: true,
  },
]

useRouter / useRouteの型推論

Composition API内で使う useRouter()useRoute() は、型推論が効いた状態で利用できます。

<script setup lang="ts">
import { useRoute, useRouter } from 'vue-router'

const router = useRouter()  // Router 型
const route = useRoute()    // RouteLocationNormalizedLoaded 型

// route.params.id は string | string[] 型
const userId = route.params.id as string
</script>

metaフィールドの型拡張

ルートの meta に独自の型を定義するには、モジュール拡張を使います。

// src/router/types.ts
declare module 'vue-router' {
  interface RouteMeta {
    requiresAuth?: boolean
    requiredRole?: string
    title?: string
  }
}

この定義を追加すると、to.meta.requiresAuth にアクセスする際にエディタの補完が有効になり、型安全なメタデータ管理が可能です。

スクロール位置の制御

SPAでは画面遷移時のスクロール位置がデフォルトでは保持されません。Vue Routerの scrollBehavior オプションでこの挙動をカスタマイズできます。

const router = createRouter({
  history: createWebHistory(),
  routes,
  scrollBehavior(to, from, savedPosition) {
    // ブラウザの戻る・進むボタンの場合は前回位置を復元
    if (savedPosition) {
      return savedPosition
    }

    // ハッシュ付きURLの場合はその要素までスクロール
    if (to.hash) {
      return {
        el: to.hash,
        behavior: 'smooth',
      }
    }

    // それ以外はページ先頭にスクロール
    return { top: 0 }
  },
})

savedPosition はブラウザの戻る・進む操作時にのみ値が入ります。通常のリンクによる遷移ではページ先頭に戻り、アンカーリンクの場合はその要素位置までスムーズスクロールする、という一般的なWebサイトの挙動を再現できます。

よくあるエラーと対処法

Vue Router pushで遷移しない原因として多いのが、現在と同じルートへ router.push() を呼んだ場合に発生する NavigationDuplicated エラーです。

// エラーが発生するケース
router.push({ name: 'search', query: { q: 'vue' } })
// 同じクエリで再度pushすると NavigationDuplicated

// 対処法1: catchで無視する
router.push({ name: 'search', query: { q: 'vue' } }).catch(() => {})

// 対処法2: 遷移前にルートを比較する
function safeNavigate(to: RouteLocationRaw) {
  const resolved = router.resolve(to)
  if (resolved.fullPath !== route.fullPath) {
    router.push(to)
  }
}

Vue Router 404ページの設定

存在しないURLにアクセスされた場合に404ページを表示するには、キャッチオールルートを定義します。

const routes = [
  // 他のルート定義...

  // キャッチオールルート(必ず最後に配置)
  {
    path: '/:pathMatch(.*)*',
    name: 'not-found',
    component: () => import('../views/NotFound.vue'),
  },
]

/:pathMatch(.*)* は正規表現ですべてのパスにマッチします。他のルートのいずれにもマッチしなかった場合にのみ適用されるよう、配列の最後に配置してください。

HTML5 History モードを使っている場合は、サーバー側のフォールバック設定と組み合わせることで、サーバーの404ではなくVueアプリ内のNotFoundコンポーネントが表示されます。

ハッシュモードからHistoryモードへの切り替え時の問題

createWebHashHistory から createWebHistory へ移行する際に、既存のURLが壊れることがあります。移行時は以下を確認してください。

  • サーバーでフォールバック設定を追加済みか
  • 外部サイトやブックマークに # 付きURLが残っていないか
  • # 付きURLから # なしURLへの301リダイレクトをサーバーで設定する

Unplugin Vue Routerによるファイルベースルーティング

Unplugin Vue Routerは、ファイルシステムの構造からルート定義を自動生成するビルドプラグインです。Vue Router 5ではこの機能がコアに統合されており、元のリポジトリはアーカイブ済みです(出典: GitHub vuejs/router)。

ファイル構造とルートの対応

src/pages/
├── index.vue          → /
├── about.vue          → /about
├── users/
│   ├── index.vue      → /users
│   └── [id].vue       → /users/:id
└── [...path].vue      → /:path(.*)* (404)

手動でルート配列を書く必要がなく、ファイルを追加するだけでルートが生成されます。TypeScriptの型も自動で生成されるため、router.push() の引数で存在しないルート名を指定するとコンパイルエラーになります。

Vue Routerが不要なケース

すべてのVue.jsアプリケーションにVue Routerが必要なわけではありません。

  • ダッシュボードなどの単一画面アプリケーション
  • 複数のHTMLページで構成されるMPA(Multi-Page Application)
  • Vue.jsを部分的に組み込んだサーバーレンダリングアプリケーション

これらの場合は v-if による条件分岐や、サーバー側のルーティングで十分対応できます。

まとめ

Vue Routerは、Vue.jsでSPAを構築する際の標準ルーティングソリューションです。基本的なルート定義から始めて、ナビゲーションガードによるアクセス制御、遅延読み込みによるパフォーマンス最適化、TypeScriptとの連携まで、実務で必要な機能を幅広くカバーしています。

主要なポイントを整理します。

  • ルート定義: 静的・動的・ネスト・名前付きの4パターンを使い分ける
  • 画面遷移: テンプレートでは <RouterLink>、ロジック内では router.push() を使用する
  • アクセス制御: beforeEach でグローバルな認証チェック、beforeEnter でルート単位の制御を実装する
  • 履歴モード: 本番環境では createWebHistory を選択し、サーバーのフォールバック設定を忘れない
  • パフォーマンス: 動的インポートでルート単位のコード分割を適用する
  • 大規模開発: Unplugin Vue Routerのファイルベースルーティングで保守性を高める

公式ドキュメント(Vue Router)には、本記事で取り上げた内容のさらに詳細な解説やAPIリファレンスが掲載されています。