タイトル: ルーティング設定
SEOタイトル: Vue.js ルーティング (Vue Router) 完全ガイド
この本文には実行可能なタグ等が含まれるため、安全のためHTMLソースとして表示しています。本文データは変更していません。
<table border="1" cellpadding="6" cellspacing="0" style="border-collapse:collapse;background:#fffbea;border:1px solid #f4c430;margin:1em 0;width:100%"><tr><td style="background:#f4c430;color:#000;font-weight:bold;padding:8px">この記事の要点</td></tr><tr><td style="padding:10px"><ul><li><strong>Vue Router</strong> は Vue.js 公式の SPA ルーティングライブラリ</li><li>Vue 3 では <code>vue-router@4</code>、Vue 2 では <code>vue-router@3</code> を使用</li><li>基本: <code>createRouter()</code> + <code>createWebHistory()</code> + <code>routes</code> 配列を <code>app.use(router)</code></li><li>テンプレートでは <strong><router-view></strong> で表示、<strong><router-link to="/"></strong> で遷移</li><li>動的セグメント <code>/user/:id</code> は <code>useRoute().params.id</code> で取得、Lazy Loading は <code>() => import()</code></li></ul></td></tr></table>
<h2>Vue Router とは</h2>
<p><strong>Vue Router</strong> は Vue.js 公式のクライアントサイドルーティングライブラリです。SPA (Single Page Application) において、URL と Vue コンポーネントを対応付け、ページ遷移時にコンポーネントを切り替えます。サーバーへのリクエストは発生せず、ブラウザの History API でアドレスバーを書き換えます。</p>
<table border="1" cellpadding="6" cellspacing="0" style="border-collapse:collapse;width:100%">
<thead><tr style="background:#e3f2fd"><th>Vue バージョン</th><th>Vue Router バージョン</th><th>主な API</th></tr></thead>
<tbody>
<tr><td>Vue 3</td><td>vue-router@4.x</td><td><code>createRouter</code> / <code>createWebHistory</code></td></tr>
<tr><td>Vue 2</td><td>vue-router@3.x</td><td><code>new VueRouter()</code> / <code>mode: 'history'</code></td></tr>
</tbody>
</table>
<h2>インストール</h2>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-bash"># Vue 3 の場合
npm install vue-router@4
# Vue 2 の場合
npm install vue-router@3
# Vite で新規作成時はオプションで含められる
npm create vue@latest
# → "Add Vue Router for Single Page Application development?" → Yes</code></pre>
<h2>基本セットアップ (Vue 3 + Composition API)</h2>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-javascript">// src/router/index.js
import { createRouter, createWebHistory } from 'vue-router';
import Home from '@/views/Home.vue';
import About from '@/views/About.vue';
const routes = [
{ path: '/', name: 'home', component: Home },
{ path: '/about', name: 'about', component: About },
// Lazy Loading(コード分割)
{ path: '/contact', component: () => import('@/views/Contact.vue') },
];
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes,
});
export default router;</code></pre><pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-javascript">// src/main.js
import { createApp } from 'vue';
import App from './App.vue';
import router from './router';
createApp(App)
.use(router)
.mount('#app');</code></pre>
<h2>テンプレートでの利用</h2>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-html"><!-- App.vue -->
<template>
<nav>
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link> |
<router-link :to="{ name: 'contact' }">Contact</router-link>
</nav>
<!-- ここに各ルートのコンポーネントが表示される -->
<router-view />
</template></code></pre>
<p><code><router-link></code> は内部的に <code><a href></code> を生成しますが、クリック時に History API を使うため、ページリロードは発生しません。アクティブな状態には自動で <code>router-link-active</code> / <code>router-link-exact-active</code> クラスが付きます。</p>
<h2>history モードの違い</h2>
<table border="1" cellpadding="6" cellspacing="0" style="border-collapse:collapse;width:100%">
<thead><tr style="background:#e3f2fd"><th>モード</th><th>URL 例</th><th>サーバ設定</th></tr></thead>
<tbody>
<tr><td><code>createWebHistory()</code></td><td><code>/about</code></td><td>★必須: 全 URL を index.html にフォールバック</td></tr>
<tr><td><code>createWebHashHistory()</code></td><td><code>/#/about</code></td><td>不要(# 以降はサーバへ送られない)</td></tr>
<tr><td><code>createMemoryHistory()</code></td><td>なし</td><td>SSR / テスト用</td></tr>
</tbody>
</table>
<h2>動的セグメント (URL パラメータ)</h2>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-javascript">// router/index.js
const routes = [
{ path: '/user/:id', name: 'user', component: User },
// 複数のパラメータ
{ path: '/post/:category/:slug', component: Post },
// オプショナル + 正規表現
{ path: '/items/:id(\\d+)?', component: Items }, // 数字のみ、省略可
];</code></pre><pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-html"><!-- User.vue -->
<script setup>
import { useRoute, useRouter } from 'vue-router';
import { watch } from 'vue';
const route = useRoute(); // 現在のルート情報(params, query, path, name)
const router = useRouter(); // ルーター本体(push, replace, back)
console.log(route.params.id); // /user/42 → "42"
console.log(route.query.tab); // /user/42?tab=posts → "posts"
// パラメータ変更を監視(同じコンポーネントの使い回し時に必須)
watch(() => route.params.id, (newId, oldId) => {
console.log(`id changed: ${oldId} → ${newId}`);
// 再フェッチ等
});
function goHome() {
router.push('/'); // パス指定
router.push({ name: 'home' }); // ルート名指定
router.push({ name: 'user', params: { id: 99 } });
router.replace('/about'); // 履歴に残さない
router.back(); // 戻る
}
</script>
<template>
<div>User ID: {{ route.params.id }}</div>
</template></code></pre>
<h2>ネストルート</h2>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-javascript">const routes = [
{
path: '/user/:id',
component: User,
children: [
// /user/:id/profile
{ path: 'profile', component: UserProfile },
// /user/:id/posts
{ path: 'posts', component: UserPosts },
// /user/:id(デフォルト子ルート)
{ path: '', component: UserHome },
],
},
];</code></pre>
<p>親コンポーネント側でも <code><router-view /></code> を置くと、その位置に子ルートのコンポーネントが描画されます。</p>
<h2>ナビゲーションガード</h2>
<p>ルート遷移の前後にフックを差し込み、認証チェックやリダイレクトを実装できます。</p>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-javascript">// グローバルガード
router.beforeEach((to, from, next) => {
if (to.meta.requiresAuth && !isLoggedIn()) {
next({ name: 'login', query: { redirect: to.fullPath } });
} else {
next();
}
});
// 解決後(次画面のコンポーネントロード後)
router.beforeResolve(async (to) => {
if (to.meta.fetchOnEnter) {
await loadData(to);
}
});
router.afterEach((to, from) => {
// 画面遷移後のアナリティクス送信など
gtag('event', 'page_view', { page_path: to.fullPath });
});
// ルート別ガード
const routes = [
{
path: '/admin',
component: Admin,
meta: { requiresAuth: true },
beforeEnter: (to, from) => { /* ... */ },
},
];</code></pre>
<h2>Lazy Loading (コード分割)</h2>
<p>初期バンドルサイズを抑えるには、各ルートのコンポーネントを動的 import します:</p>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-javascript">const routes = [
// ❌ 同期 import: 初期バンドルに全部入る
{ path: '/heavy', component: HeavyView },
// ✅ 動的 import: 訪問時に別チャンクをロード
{ path: '/heavy', component: () => import('@/views/HeavyView.vue') },
// チャンク名を指定
{
path: '/dashboard',
component: () => import(/* webpackChunkName: "dashboard" */ '@/views/Dashboard.vue'),
},
];</code></pre>
<h2>Vue 2 (vue-router 3.x) との違い</h2>
<table border="1" cellpadding="6" cellspacing="0" style="border-collapse:collapse;width:100%">
<thead><tr style="background:#fff3e0"><th>項目</th><th>Vue 2 / vue-router 3</th><th>Vue 3 / vue-router 4</th></tr></thead>
<tbody>
<tr><td>作成</td><td><code>new VueRouter({ routes })</code></td><td><code>createRouter({ routes, history })</code></td></tr>
<tr><td>history</td><td><code>mode: 'history'</code></td><td><code>history: createWebHistory()</code></td></tr>
<tr><td>登録</td><td><code>new Vue({ router })</code></td><td><code>app.use(router)</code></td></tr>
<tr><td>取得 (Composition)</td><td><code>this.$route</code> / <code>this.$router</code></td><td><code>useRoute()</code> / <code>useRouter()</code></td></tr>
<tr><td>キャッチオール</td><td><code>path: '*'</code></td><td><code>path: '/:pathMatch(.*)*'</code></td></tr>
</tbody>
</table>
<h2>404 ページの設定</h2>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-javascript">const routes = [
// ... 他のルート
// 最後に: マッチしないものを全部ここへ
{ path: '/:pathMatch(.*)*', name: 'not-found', component: NotFound },
];</code></pre>
<h2>FAQ</h2>
<p><strong>Q: リロードすると 404 になる</strong><br />
A: <code>createWebHistory</code> モードはサーバ側で「全 URL を index.html にフォールバック」設定が必要です。Nginx なら <code>try_files $uri $uri/ /index.html;</code>、Laravel なら <code>Route::view('/{any}', 'app')->where('any', '.*');</code>。</p>
<p><strong>Q: 同じコンポーネントでパラメータだけ変わったときに再生成されない</strong><br />
A: Vue Router の意図的な最適化です。<code>watch(() => route.params, ...)</code> で監視するか、<code><router-view :key="$route.fullPath" /></code> で強制再生成します。</p>
<p><strong>Q: Vue Router で SSR したい</strong><br />
A: Nuxt 3 を使うのが最短です。手動なら <code>createMemoryHistory()</code> + サーバで <code>router.push()</code> → <code>router.isReady()</code> 待ち → レンダリングのフローになります。</p>