Vue Routerのルートには、metaという自由な情報を持たせられます。ログインが必要か、どの権限が必要か、ページのタイトルは何か、といった、ルーティングの補足情報をまとめておく場所です。この記事では、Vue Router 4を前提に、metaを使った画面の出し分けとタイトルの更新、設計のコツを整理します。
ルートのガードで画面を出し分けても、ブラウザ上のコードなので、改ざんや、APIへの直接のアクセスは止められません。ここで説明するのは、使い勝手のための表示制御です。ユーザーの権限の検証は、必ずサーバー側(API)で行ってください。
metaを定義する
ルートの定義に、metaを書きます。親のルートに書いたmetaは、子のルートにも引き継がれます。たとえば/admin配下は、親にrequiresAuthとrolesを書くだけで、子のページすべてに効きます。
// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import Home from '../views/Home.vue'
import Login from '../views/Login.vue'
import Forbidden from '../views/Forbidden.vue'
import AdminLayout from '../views/admin/AdminLayout.vue'
import AdminHome from '../views/admin/AdminHome.vue'
import AdminUsers from '../views/admin/AdminUsers.vue'
const routes = [
{ path: '/', name: 'Home', component: Home, meta: { title: 'ホーム', showInMenu: true } },
{ path: '/login', name: 'Login', component: Login, meta: { title: 'ログイン' } },
{ path: '/forbidden', name: 'Forbidden', component: Forbidden, meta: { title: 'アクセス権限がありません' } },
{
path: '/admin',
component: AdminLayout,
// 親に書いた meta は、子のルートにも引き継がれる
meta: { requiresAuth: true, roles: ['admin'] },
children: [
{ path: '', name: 'AdminHome', component: AdminHome, meta: { title: '管理者ページ' } },
{ path: 'users', name: 'AdminUsers', component: AdminUsers, meta: { title: 'ユーザー管理' } }
]
}
]
export const router = createRouter({
history: createWebHistory(),
routes
})
ガードでmetaを使って遷移を制御する
router.beforeEachは、遷移のたびに呼ばれます。現在の推奨は、next()を使わず、関数の戻り値で制御する書き方です。
falseを返す:遷移を中止する- ルートの場所(
{ name: 'Login' }など)を返す:そのルートへリダイレクトする undefinedまたはtrueを返す(何も返さない):遷移を許可する
// router/index.js(続き)
import { useAuthStore } from '../stores/auth'
router.beforeEach((to) => {
// ストアは、ガードの関数の中で呼ぶ(外で呼ぶと、Pinia の準備前に実行される)
const auth = useAuthStore()
// ① ログインが必要なのに、ログインしていない → ログインページへ
// ログイン後に戻れるよう、元のURLを redirect に付ける
if (to.meta.requiresAuth && !auth.isLoggedIn) {
return { name: 'Login', query: { redirect: to.fullPath } }
}
// ② 必要な権限がない → 権限エラーのページへ
if (to.meta.roles && !to.meta.roles.includes(auth.role)) {
return { name: 'Forbidden' }
}
// 何も返さない(undefined)と、そのまま遷移が許可される
})
未ログインのときは、query: { redirect: to.fullPath }で、元のURLをログインページに渡します。ログイン後に、その場所へ戻すためです。
<script setup>
import { useRoute, useRouter } from 'vue-router'
const route = useRoute()
const router = useRouter()
async function onLogin() {
// ...ログイン処理...
// redirect があればそこへ、なければトップへ
const redirect = typeof route.query.redirect === 'string' ? route.query.redirect : '/'
router.push(redirect)
}
</script>
リダイレクトのガードでは、無限ループに注意してください。ログインページ自体にrequiresAuthを付けると、ログインページからログインページへ送り続けます。上のコードでは、LoginとForbiddenにrequiresAuthとrolesを付けていないので、ループしません。
next()を使う旧来の書き方
next()は非推奨ですが、引き続き動きます。既存のコードで見かけるため、書き方も押さえておきましょう。使う場合は、すべての分岐で、ちょうど1回だけ呼ぶ必要があります。呼び忘れや重複があると、遷移が止まったり、エラーになります。
// 旧来の next() を使う書き方(非推奨だが、引き続き動く)
router.beforeEach((to, from, next) => {
if (to.meta.requiresAuth && !isLoggedIn) {
next({ name: 'Login' }) // どの分岐でも、必ず1回だけ next を呼ぶ
} else {
next()
}
})
認証状態の判定や、ページを再読み込みしたときの復元など、認証の流れそのものは認証付きルーティングを実装する方法で解説しています。ストアの基本はPiniaを使った状態管理の始め方をご覧ください。
metaの親子のマージとto.matched
to.metaは、親から子へ再帰せずにマージされたmetaです。同じキーがあれば、子の値が優先されます。親と子のmetaを別々に見たいときは、to.matchedを使います。
// to.meta:親から子へマージ済みの meta(通常はこれで十分)
if (to.meta.requiresAuth) { /* ... */ }
// to.matched:親子それぞれの meta を個別に見たいとき
const needsAuth = to.matched.some(record => record.meta.requiresAuth)
ページタイトルを遷移ごとに更新する
router.afterEachは、遷移が終わった後に呼ばれます。第3引数のfailureには、遷移の失敗が入るので、失敗したときは何もしません。titleが文字列であることも確認します。
// router/index.js(続き)
const APP_NAME = 'My App'
router.afterEach((to, from, failure) => {
// 遷移が失敗したときは何もしない
if (failure) return
// title が文字列のときだけ、ページ名を付けて更新する
document.title =
typeof to.meta.title === 'string' ? `${to.meta.title} | ${APP_NAME}` : APP_NAME
})
なお、ブラウザ側でdocument.titleを書き換えるだけでは、検索エンジンのクローラーにどう見えるかは、環境によって変わります。検索結果での表示を重視するなら、サーバーサイドレンダリングや事前生成も検討してください。
TypeScriptでmetaに型を付ける
RouteMetaインターフェースを拡張すると、metaのキーに型が付き、打ち間違いを防げます。
// env.d.ts など
import 'vue-router'
declare module 'vue-router' {
interface RouteMeta {
title?: string
requiresAuth?: boolean
roles?: string[]
}
}
metaのそのほかの使い道:メニューの自動生成
metaに表示用の情報を持たせると、ナビゲーションも自動で作れます。ルートの定義を1か所にまとめられるので、追加や変更が楽になります。
<script setup>
import { computed } from 'vue'
import { useRouter } from 'vue-router'
const router = useRouter()
// meta.showInMenu が true のルートだけを、ナビゲーションに並べる
const menuRoutes = computed(() =>
router.getRoutes().filter(route => route.meta.showInMenu)
)
</script>
<template>
<nav>
<RouterLink v-for="route in menuRoutes" :key="route.path" :to="route.path">
{{ route.meta.title }}
</RouterLink>
</nav>
</template>
関連記事
- Vue Routerの基本はルーティングによる画面切り替え
- ネストしたルートはVue Routerで多階層のルート定義を整理する方法
- ページ遷移中の表示はページ遷移時にローディング画面を表示する方法
よくある質問(FAQ)
next()を使う場合は、すべての分岐でちょうど1回だけ呼んでください。to.metaは、親から子へ(再帰せずに)マージされます。子で同じキーを書けば、子の値が優先されます。requiresAuthを付けない、または、すでにそのページなら送らない条件を入れてください。まとめ
metaを使うと、ログインや権限の条件、タイトル、メニューの情報をルートの定義に集約できます。ガードは戻り値で書き、無限リダイレクトとログイン後の戻り先に気をつけます。ただし、クライアント側のガードは表示制御にすぎないので、権限の検証は必ずサーバー側でも行ってください。

