Vue 3 および Vue Router 4 のルーティング:`createRouter`、動的ルーティング、ネストされたルート、および遅延読み込み
Vue Router は Vue の公式の ルーティングマネージャー です。これにより、SPA(シングルページアプリケーション)に複数の「ページ」を持たせることができ、各 URL が 1 つのコンポーネントに対応するようになります。Vue Router 4 は Vue 3 向けに設計されたバージョンであり、Composition API を完全にサポートしています。
SPAのルーティングの中核をなすのは、URL ↔ コンポーネントのマッピングです。Vue Router 4を習得することは、ある程度の複雑さを持つVueアプリケーションを開発する上で不可欠なスキルです。
1. 学習内容
- createRouter および createWebHistory の初期化
- 5 種類のルート:静的、動的、ネスト型、名前付き、ワイルドカード
- プログラムによるナビゲーション(router.push / replace / go)
- ルートの遅延読み込み(動的インポート)
- ルートパラメータ(params / query)
- ルートガード(beforeEach / beforeEnter)
- 5つの主要な実例
2. シングルページアプリケーションの「404の悪夢」
(1) 課題:5ページ、重複コードが5箇所
Aliceの管理画面には、5つのページ(ホーム/製品/注文/ユーザー/設定)がありました:
VUE
<!-- ❌ The "Flip" Version: Use v-if to switch 5 components -->
<template>
<div>
<button @click="currentPage = 'home'">Home</button>
<button @click="currentPage = 'products'">Products</button>
<button @click="currentPage = 'orders'">Orders</button>
<div v-if="currentPage === 'home'"><HomePage /></div>
<div v-else-if="currentPage === 'products'"><ProductsPage /></div>
<div v-else-if="currentPage === 'orders'"><OrdersPage /></div>
<div v-else>Page not found</div>
</div>
</template>
4つの質問:
- URLは変更されない:ページを更新すると、現在のページが失われてしまう
- リンクの共有不可:URLをコピーして同僚と共有することはできません
- ブラウザの「進む」「戻る」ボタンがない:ユーザー体験が劣る
- SEO対策が不十分:検索エンジンが内容を理解できない
(2) Vue Router 4 の解決策:5つのルート + 5つのコンポーネント
JS
// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
const routes = [
{ path: '/', component: () => import('@/views/HomePage.vue') },
{ path: '/products', component: () => import('@/views/ProductsPage.vue') },
{ path: '/orders', component: () => import('@/views/OrdersPage.vue') },
{ path: '/users', component: () => import('@/views/UsersPage.vue') },
{ path: '/settings', component: () => import('@/views/SettingsPage.vue') }
]
const router = createRouter({
history: createWebHistory(),
routes
})
export default router
VUE
<!-- App.vue -->
<template>
<nav>
<router-link to="/">Home</router-link>
<router-link to="/products">Products</router-link>
<router-link to="/orders">Orders</router-link>
</nav>
<!-- ✅ Key:router-view Render the component for the current route -->
<router-view />
</template>
5つのルート + 1つのルータービュー = 完全なSPA。URLは自動的に同期され、ブラウザの「進む」ボタンと「戻る」ボタンも期待通りに動作します。
(3) 収益
Vue Router を使用した後は:
- URL同期:自動同期。既存のデータを保持したまま更新されます
- 共有可能:すべてのページにURLがあります
- ブラウザの履歴:[次へ]/[戻る]ボタンは期待通りに動作します
- SEO対応:SSR対応(Nuxt 3)
- コード量:5つのv-ifディレクティブ → routes配列の5行 (-80%)
3. ルートの初期化
(1) 初期化コードを完成させる
JS
// src/router/index.js
import { createRouter, createWebHistory, createWebHashHistory } from 'vue-router'
// 1. Define Routes(Lazy Loading)
const routes = [
{ path: '/', component: () => import('@/views/HomePage.vue') },
{ path: '/products', component: () => import('@/views/ProductsPage.vue') }
]
// 2. Create router Examples
const router = createRouter({
// history Mode: URL without # (Recommended)
history: createWebHistory(),
// hash Mode: URL with # (Compatibility with older browsers)
// history: createWebHashHistory(),
routes,
// Scrolling Behavior:Scroll to the top when switching routes
scrollBehavior(to, from, savedPosition) {
return savedPosition || { top: 0 }
}
})
export default router
(2) main.js の登録
JS
// src/main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
const app = createApp(App)
app.use(router) // Registration Routes
app.mount('#app')
(3) 2つの主要な歴史モデルの比較
| 属性 | createWebHistory | createWebHashHistory |
|---|---|---|
| URLの形式 | /products |
/#/products |
| SEO | ✅ SEO対策済み | ❌ SEO対策未対応 |
| サーバー設定 | 必須 (try_files) | 必須ではない |
| ブラウザの互換性 | IE10以降 | IE8以降 |
| 評価 | ⭐⭐⭐⭐⭐ | ⭐⭐ |
4. 5種類のルーティング設定
(1) 静的ルーティング
JS
const routes = [
{ path: '/', component: () => import('@/views/HomePage.vue') },
{ path: '/about', component: () => import('@/views/AboutPage.vue') },
{ path: '/contact', component: () => import('@/views/ContactPage.vue') }
]
(2) 動的ルーティング(パラメータ)
JS
const routes = [
// /products/123
{
path: '/products/:id',
component: () => import('@/views/ProductDetailPage.vue')
},
// /users/456/posts/789
{
path: '/users/:userId/posts/:postId',
component: () => import('@/views/PostPage.vue')
},
// Optional Parameters
{
path: '/news/:category?',
component: () => import('@/views/NewsPage.vue')
}
]
VUE
<!-- ProductDetailPage.vue Receive Parameters -->
<script setup>
import { useRoute } from 'vue-router'
const route = useRoute()
console.log(route.params.id) // '123'
</script>
(3) ネストされたルート
JS
const routes = [
{
path: '/users',
component: () => import('@/views/UsersPage.vue'),
children: [
{
path: '', // Default Subroute
component: () => import('@/views/UsersListPage.vue')
},
{
path: ':id', // /users/123
component: () => import('@/views/UserDetailPage.vue')
}
]
}
]
VUE
<!-- UsersPage.vue -->
<template>
<div>
<h1>Users</h1>
<router-view /> <!-- Subnet Route Outbound -->
</div>
</template>
(4) 名前付きルーティング
JS
const routes = [
{
path: '/products/:id',
name: 'product-detail', // Named Routing
component: () => import('@/views/ProductDetailPage.vue')
}
]
VUE
<!-- Navigate via name, Avoid hard-coding URL -->
<router-link :to="{ name: 'product-detail', params: { id: 123 } }">
Product 123
</router-link>
(5) ワイルドカード 404
JS
const routes = [
{ path: '/', component: () => import('@/views/HomePage.vue') },
// ... Other Routes
{
path: '/:pathMatch(.*)*', // Match all undefined routes
component: () => import('@/views/NotFoundPage.vue')
}
]
5. 5種類のナビゲーション
(1) 宣言型ナビゲーション (router-link)
VUE
<template>
<!-- String Path -->
<router-link to="/products">Products</router-link>
<!-- Named Routing(Recommendations) -->
<router-link :to="{ name: 'products' }">Products</router-link>
<!-- Dynamic Parameters -->
<router-link :to="{ name: 'product-detail', params: { id: 123 } }">
Product 123
</router-link>
<!-- Query Parameters -->
<router-link :to="{ path: '/search', query: { q: 'vue' } }">
Search
</router-link>
</template>
(2) プログラムによるナビゲーション (router.push)
JS
import { useRouter } from 'vue-router'
const router = useRouter()
function goToProduct(id) {
router.push(`/products/${id}`) // String
// or
router.push({ name: 'product-detail', params: { id } }) // Object
}
function goBack() {
router.back() // Back
}
function goForward() {
router.forward() // Forward
}
function replaceCurrent() {
router.replace('/login') // Replace(Leaving No Trace of History)
}
(3) 「push」と「replace」の5つの違い
| メソッド | 動作 | 履歴 |
|---|---|---|
router.push() |
新しいページへ | このページを保存(戻ることができます) |
router.replace() |
現在のページを置き換える | 痕跡を残さない(戻ることができない) |
router.go(n) |
nステップ前進/後退 | 履歴を表示 |
router.back() |
一歩引いて考える | 歴史を探る |
router.forward() |
一歩前進 | 歴史を歩む |
(4) 手続き型プログラミングと宣言型プログラミングの比較
| シナリオ | 推奨事項 |
|---|---|
| ユーザーがリンクをクリックする | router-link(宣言型) |
| JSによるナビゲーション | router.push(プログラムによる呼び出し) |
| フォーム送信後のリダイレクト | router.push |
| ログイン後のリダイレクト | router.replace |
| 404 自動リダイレクト | router.back |
6. ルートガード(ナビゲーションガード)
(1) ガードの3種類
JS
// 1. Global Frontline Guard(Most Commonly Used)
router.beforeEach((to, from, next) => {
const isLoggedIn = !!localStorage.getItem('token')
if (to.meta.requiresAuth && !isLoggedIn) {
next('/login') // Skip Login
} else {
next() // Clearance
}
})
// 2. Dedicated Route Guard
const routes = [
{
path: '/admin',
component: () => import('@/views/AdminPage.vue'),
beforeEnter: (to, from, next) => {
if (isAdmin()) next()
else next('/403')
}
}
]
// 3. In-Component Guard
// Within the component:beforeRouteEnter / beforeRouteUpdate / beforeRouteLeave
(2) ログイン保護の完全な例
JS
// router/index.js
router.beforeEach((to, from, next) => {
const token = localStorage.getItem('token')
const isLoggedIn = !!token
// Pages that require login
if (to.meta.requiresAuth && !isLoggedIn) {
return next({
name: 'login',
query: { redirect: to.fullPath } // Redirect after logging in
})
}
// Already logged in. Go to the login page.
if (to.name === 'login' && isLoggedIn) {
return next({ name: 'home' })
}
next()
})
JS
// Routing meta Layout
const routes = [
{
path: '/dashboard',
component: () => import('@/views/DashboardPage.vue'),
meta: { requiresAuth: true } // You must log in
}
]
7. 完全な例:Eコマースのバックエンドルーティングシステム
(1) ▶ サンプル:. 5種類のルーティング設定
JS
const routes = [
// 1. Static Routing
{ path: '/', name: 'home', component: () => import('@/views/HomePage.vue') },
// 2. Dynamic Routing
{
path: '/products/:id',
name: 'product-detail',
component: () => import('@/views/ProductDetailPage.vue')
},
// 3. Nested Routes
{
path: '/admin',
component: () => import('@/views/AdminLayout.vue'),
meta: { requiresAuth: true },
children: [
{ path: '', name: 'admin-dashboard', component: () => import('@/views/DashboardPage.vue') },
{ path: 'products', name: 'admin-products', component: () => import('@/views/AdminProductsPage.vue') },
{ path: 'orders', name: 'admin-orders', component: () => import('@/views/AdminOrdersPage.vue') }
]
},
// 4. Named Routing
{
path: '/user/:userId',
name: 'user-profile',
component: () => import('@/views/UserProfilePage.vue')
},
// 5. 404
{ path: '/:pathMatch(.*)*', name: 'not-found', component: () => import('@/views/NotFoundPage.vue') }
]
(2) ▶ サンプル:. ルートの遅延読み込み(5つのコンポーネントのオンデマンド読み込み)
JS
const routes = [
// ✅ Recommendations:News import
{ path: '/', component: () => import('@/views/HomePage.vue') },
{ path: '/products', component: () => import('@/views/ProductsPage.vue') }
]
(3) ▶ サンプル:. ナビゲーションの5つの種類
VUE
<!-- 1. String Path -->
<router-link to="/products">Products</router-link>
<!-- 2. Named Routing(Recommendations)-->
<router-link :to="{ name: 'products' }">Products</router-link>
<!-- 3. Dynamic Parameters -->
<router-link :to="{ name: 'product-detail', params: { id: 123 } }">
Product 123
</router-link>
<!-- 4. Query Parameters -->
<router-link :to="{ path: '/search', query: { q: 'vue' } }">
Search
</router-link>
<!-- 5. Programmatic Navigation -->
<button @click="goToProduct(123)">Go to Product</button>
JS
import { useRouter } from 'vue-router'
const router = useRouter()
function goToProduct(id) {
router.push({ name: 'product-detail', params: { id } })
}
(4) ▶ サンプル:. ログインガードの完成
JS
router.beforeEach((to, from, next) => {
const token = localStorage.getItem('token')
const isLoggedIn = !!token
if (to.meta.requiresAuth && !isLoggedIn) {
return next({ name: 'login', query: { redirect: to.fullPath } })
}
if (to.name === 'login' && isLoggedIn) {
return next({ name: 'home' })
}
next()
})
(5) ▶ サンプル:. よくある5つの間違いに関するクイックリファレンス
| エラー | 症状 | 解決策 |
|---|---|---|
| ルータービューが表示されていません | ページが表示されません | <router-view /> を追加 |
| ルートは遅延読み込みされない | 最初の画面の読み込みが遅い | 動的インポートには () => import() を使用 |
| Infinite Loop | 抜け出せない | next() が beforeEach を再度呼び出さないようにする |
| 出口のないネストされたルーティング | サブページが表示されない | 親コンポーネントに <router-view /> を追加 |
| 404 ページが見つかりません | エラー | path: '/:pathMatch(.*)*' を追加 |
(6) ▶ サンプル:. 5つの主要なパフォーマンス比較
| モード | 最初の画面 | 切り替え | 適用対象 |
|---|---|---|---|
| 静的インポート | 遅い | 速い | 小規模なプロジェクト |
| 動的インポート | ⭐⭐⭐⭐⭐ | 初回読み込みに時間がかかる | 推奨 |
| ルーティンググループの遅延読み込み | ⭐⭐⭐⭐⭐ | 初回読み込み時の速度低下 | 大規模なプロジェクト |
| プリフェッチ | ⭐⭐⭐⭐ | 高速 | ユーザー体験を最優先 |
| SSR | ⭐⭐⭐⭐⭐ | 高速 | Nuxt 3 |
❓ よくある質問
Q
createWebHistory と createWebHashHistory、どちらを選べばいいですか?A
createWebHistory(#を含まないURL、SEOに最適)はサーバーの設定が必要です。createWebHashHistory(#を含むURL)は古いブラウザにも対応しています。本番環境では createWebHistory を推奨します。Q ダイナミックルーティングはどのようにパラメータを受け取るのですか?
A
useRoute() フックを使用します:const route = useRoute(); route.params.id。Q
router.push と router.replace の違いは何ですか?A
push はブラウザの履歴に追加されます(ユーザーが「戻る」操作を行えるようになります)が、replace は現在のページを上書きします(履歴は残りません)。ログイン後は replace を使用してください(ユーザーはログインページに戻ることができないため)。Q ルーターガードは非同期で実行できますか?
A はい。
beforeEach(async (to, from, next) => { await checkAuth(); next() })。ただし、無限ループにならないよう注意してください。Q ネストされたルーティングはどのように実装しますか?
A
children: [] を使用して親ルート内で子ルートを設定し、子ルートの出口として <router-view /> を親コンポーネントのテンプレートに追加します。Q Vue Router 4 と Vue Router 3 の違いは何ですか?
A Vue Router 4 は Composition API(useRouter/useRoute)に対応しており、より強力な TypeScript の型推論を提供し、ナビゲーションガードのパラメータ(to/from/next)が変更されています。Vue 3 プロジェクトでは Vue Router 4 を使用する必要があります。
Q レイジーローディングはパフォーマンスをどの程度向上させますか?
A (まだ閲覧されていないページを読み込まないことで)最初の画面の読み込み時間を50~80%短縮できます。この効果は、大規模なプロジェクト(10ページ以上)で特に顕著です。
Q ルートガードでメタフィールドを使用するにはどうすればよいですか?
A ルート設定に
meta: { requiresAuth: true } を追加し、ガード内で to.meta.requiresAuth を使用してください。これらは、権限、タイトル、ブレッドクラムなどに使用されます。📖 まとめ
- Vue Router 4 は、Vue 3 の公式ルーティングマネージャーです
- 5 種類のルート:静的、動的、ネスト型、名前付き、ワイルドカード
- 5つのナビゲーションタイプ:router-link、router.push、replace、back、forward
- ウォッチャーの3つの種類:グローバル(beforeEach)、ルート固有(beforeEnter)、コンポーネント内部
- ルートの遅延読み込み(動的インポート)により、最初のビューが表示されるまでの時間が短縮されます
- createWebHistory の使用が推奨されます。createWebHashHistory は、古いブラウザとの下位互換性を確保するためのものです。
- 子ルートを含むネストされたルート +
<router-view />
📝 練習問題
(1) 基礎問題(難易度:⭐)
シンプルな複数ページ構成のSPAを実装する:
- 3ページ:ホーム / 会社概要 / お問い合わせ
- 上部ナビゲーション + 下部ルータービュー
vue-routerを使用してルーターを設定する
(2) 上級問題(難易度:⭐⭐)
Eコマースにおける商品ルーティングの実装:
/products一覧ページ/products/:id詳細ページ(動的パラメータ)/products/:id/editページを編集- 404エラーの処理
(3) チャレンジ問題(難易度:⭐⭐⭐)
完全なeコマース用バックエンドルーティングシステムを実装する:
- 10ページ以上(ホーム/商品/注文/ユーザー/設定/ログイン/404など)
- ネストされたルート(/admin/dashboard、/admin/products など)
- ルートの遅延読み込み(最初の画面の読み込みを高速化)
- 警備員としてログインしてください(ログインしていない場合は /login にアクセスし、ログイン済みの場合はこのページに戻ってください)
- 5つのナビゲーション手法を網羅
- プログラムによるナビゲーション(ボタンをクリックして移動)
- TypeScript による強型付けルーティングの設定



