Vue3 路由系統(Vue Router 4)—— Lazy Loading 路由
簡介
在單頁應用(SPA)中,路由是連接不同畫面的關鍵。隨著功能日益龐大,若一次性載入所有路由對應的元件,會導致首屏載入時間過長、使用者體驗下降。
Lazy Loading(懶載入)正是為了解決這個問題:只有在使用者真的要前往某個路由時,才動態下載對應的程式碼。這不僅縮短了初始載入大小,也讓瀏覽器的快取更有效率。
在 Vue3 生態中,Vue Router 4 完全支援動態匯入(import())與 Route‑Based Code Splitting,只要稍微調整路由設定,就能輕鬆實現懶載入。本篇教學將從概念說明、實作範例到最佳實踐,帶你一步步在 Vue3 專案中導入 lazy loading。
核心概念
1. 為什麼需要懶載入
- 縮短首屏載入時間:只載入當前畫面所需的 JavaScript,其他頁面的程式碼延後下載。
- 降低帶寬消耗:使用者只會下載他實際瀏覽過的路由,對行動裝置尤其友好。
- 提升效能:較小的 bundle 讓瀏覽器解析、執行速度更快,減少記憶體佔用。
2. Vue Router 4 的懶載入語法
在 Vue Router 4 中,路由的 component 屬性可以接受 動態匯入回傳的 Promise:
{
path: '/about',
component: () => import('@/views/About.vue')
}
當使用者導航到 /about 時,Webpack(或 Vite)會自動產生一個獨立的 chunk,只有在此時才會被下載。
3. 命名 chunk 以利除錯與快取
使用 /* webpackChunkName: "about" */(Webpack)或 /* @vite-ignore */(Vite)可以自行命名產出的檔案,方便除錯與快取策略:
{
path: '/about',
component: () => import(/* webpackChunkName: "about" */ '@/views/About.vue')
}
4. 結合 defineAsyncComponent 的進階寫法
Vue 3 內建 defineAsyncComponent,允許設定 載入中的占位元件、錯誤處理與 重試次數,配合路由使用可提升使用者體驗:
import { defineAsyncComponent } from 'vue'
const AsyncAbout = defineAsyncComponent({
// 匯入元件的函式
loader: () => import('@/views/About.vue'),
// 載入期間顯示的元件
loadingComponent: {
template: '<p>載入中…</p>'
},
// 發生錯誤時顯示的元件
errorComponent: {
template: '<p>載入失敗,請稍後再試。</p>'
},
// 載入逾時時間(毫秒)
timeout: 3000,
// 重試次數
retry: 2
})
export default [
{ path: '/about', component: AsyncAbout }
]
5. 多層嵌套路由的懶載入
在父子路由結構中,每一層都可以單獨懶載入,保持子路由僅在需要時才下載:
export default [
{
path: '/dashboard',
component: () => import('@/layouts/DashboardLayout.vue'), // 父層懶載入
children: [
{
path: '',
name: 'DashboardHome',
component: () => import('@/views/dashboard/Home.vue')
},
{
path: 'settings',
name: 'DashboardSettings',
component: () => import('@/views/dashboard/Settings.vue')
}
]
}
]
程式碼範例
範例 1:最簡單的懶載入路由
// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
const routes = [
{
path: '/',
name: 'Home',
component: () => import('@/views/Home.vue') // 立即載入
},
{
path: '/about',
name: 'About',
component: () => import('@/views/About.vue') // **懶載入**
}
]
export default createRouter({
history: createWebHistory(),
routes
})
說明:
Home.vue會在應用啟動時被打包進主 bundle,About.vue則會產生獨立 chunk,只有在使用者點擊「About」時才會下載。
範例 2:為 chunk 命名,提升快取效率
{
path: '/profile',
name: 'Profile',
component: () =>
import(
/* webpackChunkName: "user-profile" */
'@/views/Profile.vue'
)
}
說明:產生的檔名會是
user-profile.[hash].js,若日後需要針對使用者頁面做 CDN 快取設定,只要針對這個名稱即可。
範例 3:使用 defineAsyncComponent 加載占位與錯誤處理
import { defineAsyncComponent } from 'vue'
const AsyncContact = defineAsyncComponent({
loader: () => import('@/views/Contact.vue'),
loadingComponent: {
template: '<div class="spinner">載入中…</div>'
},
errorComponent: {
template: '<div class="error">Oops! 載入失敗。</div>'
},
timeout: 5000,
retry: 3
})
export default [
{ path: '/contact', component: AsyncContact }
]
說明:使用者在網路較慢時會看到自訂的 loading UI,若超過 5 秒仍未成功,會顯示錯誤訊息,並自動重試最多三次。
範例 4:懶載入嵌套路由(Dashboard)
export default [
{
path: '/dashboard',
component: () => import('@/layouts/DashboardLayout.vue'), // 父層懶載入
children: [
{
path: '',
name: 'DashboardHome',
component: () => import('@/views/dashboard/Home.vue')
},
{
path: 'reports',
name: 'DashboardReports',
component: () => import('@/views/dashboard/Reports.vue')
},
{
path: 'settings',
name: 'DashboardSettings',
component: () => import('@/views/dashboard/Settings.vue')
}
]
}
]
說明:即使使用者只進入
DashboardHome,Reports.vue、Settings.vue仍不會被下載,減少不必要的流量。
範例 5:結合路由守衛(Navigation Guard)與懶載入
{
path: '/admin',
name: 'Admin',
component: () => import('@/views/Admin.vue'),
beforeEnter: async (to, from, next) => {
const hasPermission = await checkUserPermission()
if (hasPermission) {
next()
} else {
next({ name: 'Forbidden' })
}
}
}
說明:在
beforeEnter中先做權限驗證,只有通過後才會真正載入Admin.vue,避免未授權使用者下載管理介面的程式碼。
常見陷阱與最佳實踐
| 陷阱 | 可能的問題 | 解決方式 |
|---|---|---|
忘記設定 webpackChunkName |
產生的 chunk 名稱是隨機的 hash,難以追蹤與快取 | 在 import() 註解中加入 webpackChunkName(或 Vite 的 /* @vite-ignore */) |
| 懶載入過度細分 | 每個小元件都產生獨立 chunk,導致大量 HTTP 請求 | 依功能模組劃分,例如把同一功能的多個頁面合併成同一個 chunk |
| 載入失敗未處理 | 網路不佳時使用者看到白屏 | 使用 defineAsyncComponent 的 errorComponent 或在路由守衛中捕獲錯誤 |
| SSR(伺服器端渲染)時未考慮 | 動態匯入在 SSR 環境可能導致 hydration 不一致 | 在 router.isReady() 前先確保所有必要的 chunk 已預取,或使用 vite-plugin-ssr 的 preload 功能 |
| 懶載入的路由未在導航列預先加載 | 使用者點擊導航列時仍會有短暫的 loading 效果 | 使用 router.prefetch()(Vue Router 4.2+)於滑鼠懸停時預先下載對應 chunk |
最佳實踐
- 按功能模組分組:例如
user/*、admin/*各自產生一個 chunk。 - 使用占位元件:提供視覺回饋,避免使用者感受到「卡住」的情況。
- 設定合理的 timeout:避免長時間等待,超時後給予重試或錯誤訊息。
- 預先加載關鍵路由:對於常用的頁面(如登入後的首頁),可以在應用啟動時使用
router.isReady()或import()手動觸發預載。 - 監控 bundle 大小:使用
webpack-bundle-analyzer或 Vite 的visualizer定期檢查懶載入的效果,確保沒有意外的巨型 chunk。
實際應用場景
| 場景 | 為何適合懶載入 |
|---|---|
| 大型企業後台系統 | 功能眾多,使用者只會常用部份模組,懶載入可大幅減少首次載入時間。 |
| 行動裝置優先的電商網站 | 手機網路不穩,懶載入商品詳情、評論等次要頁面,可提升購物流程的流暢度。 |
| 多語系平台 | 每個語系的資料表或元件可能不同,透過懶載入僅在切換語系時下載對應資源。 |
| 需要頻繁更新的功能 | 例如公告或報表頁面,使用懶載入讓新版本的程式碼可獨立部署,不影響主應用。 |
| SSR + CSR 混合模式 | 首頁使用 SSR 渲染,次要頁面使用懶載入的 CSR,兼顧 SEO 與效能。 |
總結
懶載入是 Vue3 + Vue Router 4 中提升單頁應用效能的關鍵技巧。透過簡單的 import() 語法,我們可以把每個路由的程式碼切割成獨立的 chunk,讓使用者只在需要時才下載。結合 defineAsyncComponent、命名 chunk、預先加載與錯誤處理,能打造出 快速、穩定且具備良好使用者體驗 的應用程式。
在實務開發中,建議先從「功能模組」的角度規劃懶載入策略,避免過度細分或過度集中,並持續使用分析工具監控 bundle 大小。只要掌握上述概念與最佳實踐,懶載入將成為提升 Vue3 應用效能的強大武器。祝你開發順利,寫出更快、更好用的 Vue3 應用!