vue vue-router routing SPA

[Vue] Router

前端路由的作用

  • 傳統網站:每點一個連結,瀏覽器就向伺服器要一個新的 HTML 頁面,整頁重新載入
  • SPA(Single Page Application):從頭到尾只有一個 HTML 頁面,內容靠 JavaScript 動態替換
  • 但 SPA 還是得處理 URL 對應、瀏覽器上一頁/下一頁、直接輸入網址這些行為——這就是前端路由的工作
  • Vue Router 是 Vue 官方的路由方案,負責把 URL 對應到元件

基本設定

安裝套件(create-vue 建專案時若已選 Router 則不需要):

npm install vue-router

建立路由

// 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
  }
]

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

export default router

掛載到 app

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

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

在 template 裡使用

<!-- App.vue -->
<template>
  <nav>
    <RouterLink to="/">首頁</RouterLink>
    <RouterLink to="/about">關於</RouterLink>
  </nav>

  <!-- 路由匹配到的元件會渲染在這裡 -->
  <RouterView />
</template>
  • <RouterLink> — Vue Router 版的 <a> 標籤,點了不會整頁刷新,而是走前端路由切換
  • <RouterView> — 佔位元件,當前路由匹配到的元件就渲染在這裡

createWebHistory vs createWebHashHistory

// URL 長這樣:example.com/about
createWebHistory()

// URL 長這樣:example.com/#/about
createWebHashHistory()
模式URL 格式後端配合備註
createWebHistoryexample.com/about需要(所有路徑導向 index.html)主流部署平台(Vercel、Netlify)自動處理,推薦使用
createWebHashHistoryexample.com/#/about不需要(# 後的內容不送到伺服器)URL 較不美觀

如果是自己架伺服器,後端要設 rewrite 把所有路徑指回 index.html,不然使用者一按重新整理就 404:

// vercel.json
{
  "rewrites": [{ "source": "/(.*)", "destination": "/index.html" }]
}
# nginx.conf
location / {
  try_files $uri $uri/ /index.html;
}

動態路由

URL 裡有會變動的片段(像 /user/123/post/456),就用動態路由:

const routes = [
  {
    path: '/user/:id',
    name: 'User',
    component: UserProfile
  }
]

:id 就是動態參數,在元件裡用 useRoute() 拿:

<script setup>
import { useRoute } from 'vue-router'

const route = useRoute()
console.log(route.params.id) // '123'
</script>

多個動態參數

// /user/jeremy/posts/42
{
  path: '/user/:username/posts/:postId',
  component: UserPost
}

監聽參數變化

有個坑:從 /user/123 導航到 /user/456,Vue 會複用同一個元件實例,所以 onMounted 不會重跑。想跟著參數變化做事,要用 watch

<script setup>
import { watch } from 'vue'
import { useRoute } from 'vue-router'

const route = useRoute()

watch(
  () => route.params.id,
  (newId) => {
    // 重新取得資料
    fetchUser(newId)
  }
)
</script>

巢狀路由

常見需求:外層共用一個 layout(像 sidebar),中間的內容區跟著子路由切換。

/settings
├── /settings/profile    → 個人資料
├── /settings/security   → 安全設定
└── /settings/notify     → 通知設定

用巢狀路由就能做到:

const routes = [
  {
    path: '/settings',
    component: SettingsLayout,
    children: [
      {
        path: '',          // /settings 的預設頁面
        component: SettingsProfile
      },
      {
        path: 'profile',   // /settings/profile
        component: SettingsProfile
      },
      {
        path: 'security',  // /settings/security
        component: SettingsSecurity
      },
      {
        path: 'notify',    // /settings/notify
        component: SettingsNotify
      }
    ]
  }
]

然後在 SettingsLayout.vue 裡放一個 <RouterView>,當子路由的出口:

<!-- SettingsLayout.vue -->
<template>
  <div class="settings-page">
    <aside>
      <RouterLink to="/settings/profile">個人資料</RouterLink>
      <RouterLink to="/settings/security">安全設定</RouterLink>
      <RouterLink to="/settings/notify">通知設定</RouterLink>
    </aside>

    <main>
      <!-- 子路由的元件會渲染在這裡 -->
      <RouterView />
    </main>
  </div>
</template>

核心概念:外層的 <RouterView> 渲染父路由的元件,內層的 <RouterView> 渲染子路由的元件,要疊幾層都可以。


導航守衛

導航守衛會在路由切換前攔下來,檢查過了才放行。最常見的用途就是權限控制

全域守衛

// router/index.js
router.beforeEach((to, from) => {
  const isLoggedIn = !!localStorage.getItem('token')

  // 要去的頁面需要登入,但使用者沒登入
  if (to.meta.requiresAuth && !isLoggedIn) {
    // 導向登入頁
    return { name: 'Login' }
  }
})

搭配路由的 meta 欄位:

const routes = [
  {
    path: '/dashboard',
    component: Dashboard,
    meta: { requiresAuth: true }
  },
  {
    path: '/login',
    name: 'Login',
    component: Login
  }
]

路由獨享守衛

只想管某一條路由的話,寫在路由本身:

{
  path: '/admin',
  component: Admin,
  beforeEnter: (to, from) => {
    const user = useAuthStore()
    if (user.role !== 'admin') {
      return { name: 'Home' }
    }
  }
}

元件內守衛

onBeforeRouteLeave 可以在元件裡攔截「要離開這頁」的時機,最典型的用法是提醒使用者還有沒存檔的修改:

<script setup>
import { onBeforeRouteLeave } from 'vue-router'

const hasUnsavedChanges = ref(false)

onBeforeRouteLeave((to, from) => {
  if (hasUnsavedChanges.value) {
    const answer = confirm('你有未儲存的修改,確定要離開嗎?')
    if (!answer) return false // 取消導航
  }
})
</script>

守衛的執行順序

從 A 頁面導航到 B 頁面,守衛依序執行:

  1. A 的 onBeforeRouteLeave
  2. 全域 beforeEach
  3. B 的 beforeEnter(如果有)
  4. B 的元件被解析
  5. 全域 afterEach

懶載入

如果你的 app 有 50 個頁面,使用者打開首頁的時候就要下載全部 50 個頁面的 JS?太浪費了。

懶載入(Lazy Loading)讓每個頁面的程式碼在需要的時候才載入。做法超簡單,把 import 改成動態的就好:

const routes = [
  {
    path: '/',
    component: Home  // 首頁直接載入
  },
  {
    path: '/about',
    // 動態 import → 只有使用者點進 /about 才會載入這個元件的 JS
    component: () => import('@/views/About.vue')
  },
  {
    path: '/dashboard',
    component: () => import('@/views/Dashboard.vue')
  }
]

Vite 和 Webpack 都會自動把動態 import 的部分拆成獨立的 JS 檔案(code splitting)。使用者進首頁只會下載首頁的 JS,點到 About 才會額外下載 About 的 JS。

分組打包

如果有些頁面通常會一起被訪問,你可以把它們打包在一起:

// 這三個會被打包在同一個 chunk 裡
component: () => import(/* webpackChunkName: "settings" */ '@/views/SettingsProfile.vue')
component: () => import(/* webpackChunkName: "settings" */ '@/views/SettingsSecurity.vue')
component: () => import(/* webpackChunkName: "settings" */ '@/views/SettingsNotify.vue')

不過如果你用 Vite 的話,它的 tree-shaking 已經做得很好了,大部分情況不需要手動分組。


實用技巧

程式化導航

除了用 <RouterLink>,你也可以在 JS 裡面控制導航:

import { useRouter } from 'vue-router'

const router = useRouter()

// 跳到某個路由
router.push('/about')
router.push({ name: 'User', params: { id: '123' } })

// 替換(不會留下歷史紀錄,按上一頁不會回來)
router.replace('/login')

// 前進/後退
router.go(-1) // 等同於按「上一頁」
router.go(1)  // 等同於按「下一頁」

Query 參數

// URL: /search?q=vue&page=1
router.push({ path: '/search', query: { q: 'vue', page: 1 } })

// 在元件裡取得
const route = useRoute()
console.log(route.query.q)    // 'vue'
console.log(route.query.page) // '1'(注意是字串)

404 頁面

{
  path: '/:pathMatch(.*)*',
  name: 'NotFound',
  component: () => import('@/views/NotFound.vue')
}

把這個放在路由設定的最後面,所有沒匹配到的路徑都會導到 404 頁面。

滾動行為

切換路由時預設不會回到頂部。加上 scrollBehavior 可以控制:

const router = createRouter({
  history: createWebHistory(),
  routes,
  scrollBehavior(to, from, savedPosition) {
    // 如果有儲存位置(瀏覽器上一頁/下一頁),回到那個位置
    if (savedPosition) return savedPosition
    // 否則回到頂部
    return { top: 0 }
  }
})

小結

概念一句話
基本路由URL 對應元件,用 RouterView 渲染
動態路由:id 參數,用 route.params 取得
巢狀路由children + 多層 RouterView
導航守衛路由保全,檢查權限才放行
懶載入() => import() 用到才載入

Vue Router 的東西其實還有很多(transition、keep-alive、命名視圖等),但上面這些已經涵蓋了日常開發 90% 的需求。把這些搞懂,你就能建構一個有完整路由系統的 Vue 應用了。

Latest Updates

  • 2026.06.11 Content updated