Vue3 路由系統(Vue Router 4)— meta 屬性與權限控制
簡介
在單頁應用(SPA)中,路由不只是導航的工具,更是實作「權限控管」的第一道防線。Vue Router 4 為每條路由提供了 meta 屬性,讓開發者可以在路由定義內加入自訂資訊(例如:是否需要登入、使用者角色、頁面標題…)。透過在全域守衛 (router.beforeEach) 中判斷 meta,即可在使用者嘗試進入受限頁面時,主動導向登入或錯誤頁面,提升應用的安全性與使用者體驗。
本篇文章將從概念說明、實作範例、常見陷阱與最佳實踐,帶你一步一步在 Vue3 + Vue Router 4 中完成 meta 屬性與權限控制 的完整流程,適合剛入門的開發者,也能為中階開發者提供可直接套用的實務範本。
核心概念
1. meta 是什麼?
meta 是路由物件的一個可自由擴充的欄位,內容會在路由匹配時一起被保留在 to 參數中。它不會影響路由本身的匹配規則,只是提供「額外描述」的功能。
// src/router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import Home from '@/views/Home.vue'
import Dashboard from '@/views/Dashboard.vue'
const routes = [
{
path: '/',
name: 'Home',
component: Home,
meta: { title: '首頁' } // 只負責顯示頁面標題
},
{
path: '/dashboard',
name: 'Dashboard',
component: Dashboard,
meta: {
title: '管理後台',
requiresAuth: true, // 必須登入才能進入
allowedRoles: ['admin', 'editor'] // 允許的使用者角色
}
}
]
export default createRouter({
history: createWebHistory(),
routes
})
2. 為什麼在 meta 放權限資訊?
- 集中管理:所有與路由相關的權限條件都寫在同一個檔案,方便檢視與維護。
- 路由守衛可直接取得:在
router.beforeEach內,to.meta會自動帶入,省去額外的查詢或狀態管理。 - 彈性擴充:除了
requiresAuth、allowedRoles,還可以加入breadcrumb、keepAlive、layout等自訂屬性。
3. 全域前置守衛的運作流程
// src/router/guard.js
import router from './index'
import store from '@/store' // 假設使用 Pinia/Vuex 管理使用者狀態
router.beforeEach((to, from, next) => {
// 1. 讀取 meta
const { requiresAuth, allowedRoles, title } = to.meta
// 2. 設定文件標題(提升 SEO 與使用者體驗)
if (title) document.title = `${title} | MyApp`
// 3. 判斷是否需要登入
if (requiresAuth && !store.state.user.isLoggedIn) {
// 未登入 → 導向登入頁,並保留原本想去的路徑
return next({ name: 'Login', query: { redirect: to.fullPath } })
}
// 4. 判斷角色權限(已登入才會檢查)
if (requiresAuth && allowedRoles) {
const userRole = store.state.user.role
if (!allowedRoles.includes(userRole)) {
// 沒權限 → 導向 403 錯誤頁
return next({ name: 'Forbidden' })
}
}
// 5. 通過所有檢查 → 正常導向
next()
})
4. 多層路由與 meta 繼承
在巢狀路由中,子路由會自動繼承父層的 meta(但不會覆寫)。若子路由需要額外條件,只要在子層再加上自己的 meta 即可。
const routes = [
{
path: '/admin',
component: () => import('@/layouts/AdminLayout.vue'),
meta: { requiresAuth: true, allowedRoles: ['admin'] },
children: [
{
path: '',
name: 'AdminHome',
component: () => import('@/views/admin/Home.vue')
},
{
path: 'settings',
name: 'AdminSettings',
component: () => import('@/views/admin/Settings.vue'),
meta: { title: '系統設定' } // 只補充標題,繼承 requiresAuth & allowedRoles
}
]
}
]
5. 動態加入或變更 meta
有時候權限資訊會依照 API 回傳結果才決定,此時可以在路由守衛裡 動態修改 to.meta,或在路由定義時使用函式返回 meta。
// 動態決定是否需要特別驗證
{
path: '/profile/:id',
name: 'UserProfile',
component: () => import('@/views/UserProfile.vue'),
meta: route => ({
title: `使用者 ${route.params.id} 的個人頁`,
// 只有本人或 admin 才能編輯
canEdit: store.state.user.id === Number(route.params.id) ||
store.state.user.role === 'admin'
})
}
在守衛中讀取:
if (to.meta.canEdit === false) {
return next({ name: 'Forbidden' })
}
程式碼範例
範例 1:最簡單的登入檢查
router.beforeEach((to, from, next) => {
if (to.meta.requiresAuth && !store.getters.isLoggedIn) {
return next({ name: 'Login' })
}
next()
})
註解:只要
meta.requiresAuth為true,且store.getters.isLoggedIn為false,即導向登入頁。
範例 2:角色權限判斷
router.beforeEach((to, from, next) => {
const { requiresAuth, allowedRoles } = to.meta
if (requiresAuth) {
const user = store.state.user
if (!user.isLoggedIn) {
return next({ name: 'Login', query: { redirect: to.fullPath } })
}
if (allowedRoles && !allowedRoles.includes(user.role)) {
return next({ name: 'Forbidden' })
}
}
next()
})
註解:先檢查登入狀態,再根據
allowedRoles判斷是否具備權限。
範例 3:自訂 404 / 403 錯誤頁面
// router/index.js
const routes = [
// ... 其他路由
{ path: '/403', name: 'Forbidden', component: () => import('@/views/Forbidden.vue') },
{ path: '/:pathMatch(.*)*', name: 'NotFound', component: () => import('@/views/NotFound.vue') }
]
// guard.js
router.beforeEach((to, from, next) => {
// 先處理權限
// ...
// 若路由不存在則自動導向 NotFound
if (!to.matched.length) return next({ name: 'NotFound' })
next()
})
註解:
/:pathMatch(.*)*為 Vue Router 4 的萬用匹配寫法,確保未定義的路由都有友善的錯誤頁面。
範例 4:使用 meta.title 動態設定頁面標題
router.afterEach(to => {
const baseTitle = 'MyApp'
document.title = to.meta.title ? `${to.meta.title} - ${baseTitle}` : baseTitle
})
註解:
afterEach在路由切換完成後執行,適合更新瀏覽器標題或埋點。
範例 5:在組件內取得權限資訊
<template>
<div>
<h1>管理後台</h1>
<button v-if="canEdit" @click="openEdit">編輯內容</button>
</div>
</template>
<script setup>
import { useRoute } from 'vue-router'
const route = useRoute()
const canEdit = route.meta.canEdit ?? false // 若未設定則為 false
function openEdit() {
// 編輯邏輯…
}
</script>
註解:
useRoute()讓組件直接存取當前路由的meta,可根據權限即時調整 UI。
常見陷阱與最佳實踐
| 陷阱 | 說明 | 解決方案 / 最佳實踐 |
|---|---|---|
忘記在守衛最後呼叫 next() |
若在 beforeEach 中未呼叫 next(),路由會卡住不前進。 |
務必在每條分支結尾呼叫 next(),或直接回傳 next({ … })。 |
meta 只在路由定義時寫死 |
動態需求(如根據 API 判斷是否開啟功能)時,靜態 meta 無法滿足。 |
使用 函式返回 meta 或在守衛內 動態改寫 to.meta。 |
| 路由守衛過於龐大 | 所有檢查寫在同一個 beforeEach,可讀性與除錯成本高。 |
拆分成多個守衛(如 authGuard、roleGuard),或使用 router.addRoute 動態註冊受限路由。 |
| 權限資訊存放在前端 | 只靠前端驗證容易被繞過。 | 後端仍需驗證;前端僅作為使用者體驗的第一層防護。 |
| 忘記處理未匹配路由 | 使用者直接輸入不存在的網址會得到空白頁。 | 設置 404 路由(/:pathMatch(.*)*),並在守衛中檢查 to.matched.length。 |
最佳實踐總結
- 統一在
meta定義權限需求,避免散落在多處程式碼。 - 在全域守衛中先處理登入,再處理角色,保持邏輯清晰。
- 使用
afterEach更新頁面標題與 SEO 資訊,提升使用者體驗。 - 將錯誤頁面(403/404)列為獨立路由,提供一致的 UI。
- 永遠在後端再次驗證,前端僅作輔助。
實際應用場景
- 企業內部系統:不同部門(HR、Finance、Admin)擁有不同功能,透過
meta.allowedRoles直接在路由層級限制。 - SaaS 多租戶平台:每個租戶的功能開關在資料庫中設定,載入後在
router.beforeEach動態調整to.meta,讓 UI 自動呈現可用功能。 - 行動端與桌面端共用程式碼:使用
meta.isMobileOnly判斷是否僅限手機使用,若在桌面瀏覽則自動導向提示頁。 - A/B 測試:在路由
meta加入experimentGroup,根據使用者屬性分配不同的 UI/功能,且不需要額外的條件分支。
總結
meta是 Vue Router 4 為路由提供的 彈性資訊容器,非常適合放置 權限需求、頁面標題、功能開關 等自訂屬性。- 透過 全域前置守衛 (
router.beforeEach) 結合 Pinia/Vuex 的使用者狀態,我們可以在使用者每次切換路由時即時檢查 登入與角色,並在不符合條件時導向 登入、403、404 等友善頁面。 - 最佳實踐 包含:統一使用
meta、分離守衛邏輯、處理未匹配路由、在afterEach更新標題、並且永遠在後端再次驗證。 - 只要掌握上述概念與範例,即可在 Vue3 專案中快速建立安全、可維護且使用者體驗佳的路由權限機制。
祝你在 Vue3 的旅程中,寫出乾淨、可靠的路由程式碼! 🚀