Vue SPA 如何優化首屏載入速度
SPA 的首屏慢,通常不是單一原因。它可能慢在 HTML 回來太晚、JS bundle 太大、主執行緒被阻塞、資料請求太多、圖片太重,或是初次渲染節點太多。
一句話回答:Vue SPA 首屏優化要先量測瓶頸,再從資源體積、分包策略、網路請求、渲染成本、快取策略與渲染模式幾個方向處理;常見手段包含路由懶載入、Tree Shaking、壓縮與 CDN、圖片優化、骨架屏、資料預取、虛擬列表、SSR/SSG。
一、 必考觀念
1. 先定義「首屏」
首屏不是一個單一指標,通常要拆成幾個階段看:
| 指標 | 意義 |
|---|---|
| TTFB | 伺服器第一個 byte 回來的時間 |
| FCP | First Contentful Paint,第一次有內容被畫出來 |
| LCP | Largest Contentful Paint,最大內容元素渲染完成 |
| TTI | Time to Interactive,頁面可互動時間 |
| INP | Interaction to Next Paint,互動延遲表現 |
| CLS | Cumulative Layout Shift,版面位移 |
面試時不用把所有縮寫背得像考證照,但要能說出:首屏速度包含網路、資源下載、JS 執行、渲染與互動可用性。
2. SPA 首屏為什麼容易慢?
典型 CSR SPA 流程:
如果 HTML 只是空殼:
<div id="app"></div>使用者真正看到內容,必須等:
- HTML 回來。
- JS bundle 下載。
- JS parse/execute。
- Router 匹配。
- 資料請求完成。
- Vue render/patch 完成。
任何一段慢,都會拖累首屏。
二、 分析方向
1. 先量測,不要憑感覺優化
常用工具:
- Chrome DevTools Performance。
- Lighthouse。
- Network 面板。
- Web Vitals。
- 打包分析工具,例如
rollup-plugin-visualizer。 - 真實使用者監控 RUM。
要先回答:
- 是 JS 太大?
- 是 API 太慢?
- 是圖片太重?
- 是 render 太多節點?
- 是 main thread 被長任務卡住?
- 是 CDN / cache 沒配置好?
不同瓶頸對應不同解法。
2. 看 Network 瀑布圖
Network 面板可以看:
- HTML 是否慢。
- JS/CSS 是否太大。
- 是否有太多阻塞資源。
- 圖片是否過大。
- API 是否串行等待。
- Cache 是否命中。
如果主 bundle 幾 MB,第一優先通常是分包與減少依賴。
如果 bundle 不大但 API 慢,就要看資料預取、並行請求、快取與 loading 策略。
三、 打包與資源體積優化
1. 路由懶載入
不要把所有頁面都塞進首屏 bundle。
const routes = [
{
path: '/',
component: () => import('@/views/Home.vue'),
},
{
path: '/admin',
component: () => import('@/views/Admin.vue'),
},
]這樣 /admin 頁面的程式碼不會在首頁首屏就下載。
這和 Vue Router 原理相關,可以延伸讀:Vue 路由實現:Hash 路由與 History 路由原理。
2. 組件級懶載入
非首屏的大型組件可以延後載入:
import { defineAsyncComponent } from 'vue'
const HeavyChart = defineAsyncComponent(() => {
return import('@/components/HeavyChart.vue')
})適合:
- 圖表。
- 富文本編輯器。
- 地圖。
- 大型表格。
- 很少打開的 Modal。
3. Tree Shaking 與依賴治理
常見問題:
- 為了一個函數引入整個工具庫。
- 圖表庫、日期庫、富文本套件進入主包。
- icon 全量引入。
- 重複依賴多版本。
優化方向:
// 避免不必要的全量引入
import _ from 'lodash'
// 優先按需引入
import debounce from 'lodash-es/debounce'更重要的是用 bundle analyzer 看真實體積,不要猜。
4. 壓縮與傳輸優化
常見手段:
- 開啟 gzip / brotli。
- JS/CSS minify。
- HTTP/2 或 HTTP/3。
- CDN。
- 靜態資源長快取。
- 產物檔名 hash 化。
典型快取策略:
index.html: no-cache 或短快取
assets/*.js: max-age=31536000, immutable原因是 JS/CSS 檔名通常帶 hash,內容變了檔名也變,可以長快取;但 index.html 要能拿到最新資源入口。
5. 圖片與字體優化
圖片常常比 JS 更拖首屏。
優化方向:
- 使用 WebP / AVIF。
- 正確尺寸,不要傳超大圖再用 CSS 縮小。
- 首屏關鍵圖預載。
- 非首屏圖片 lazy loading。
- icon 用 SVG sprite 或按需引入。
- 字體使用
font-display: swap。
<img
src="/hero.webp"
width="1200"
height="600"
fetchpriority="high"
alt=""
/>四、 資料請求優化
1. 避免瀑布流請求
壞情況:
先請求 user
-> user 回來後請求 settings
-> settings 回來後請求 dashboard如果彼此沒有依賴,應該並行:
const [user, settings, dashboard] = await Promise.all([
fetchUser(),
fetchSettings(),
fetchDashboard(),
])2. 首屏資料優先級
不是所有資料都要阻塞首屏。
可以分成:
| 類型 | 策略 |
|---|---|
| 首屏必要資料 | 優先請求,必要時阻塞渲染 |
| 首屏可延後資料 | 先渲染骨架或占位,再補資料 |
| 非首屏資料 | 滾動到附近再載入 |
| 使用者操作後才需要 | 點擊後再載入 |
3. 資料快取
常見快取層:
- HTTP cache。
- CDN cache。
- API gateway cache。
- 前端 memory cache。
- Pinia store cache。
- IndexedDB / localStorage。
例如列表頁回詳情再返回,不一定要重新請求列表。可以配合 Pinia 或 KeepAlive。
延伸讀:深入 Vue KeepAlive:組件緩存與狀態保留。
4. 預取與預載
可以在使用者即將需要時提前載入:
- hover 某個入口時預取 chunk。
- 首屏空閒時預取下一頁資料。
- 對高機率路徑做 prefetch。
但不要無腦 prefetch 所有東西,否則會搶首屏頻寬。
五、 渲染與主執行緒優化
1. 減少首屏 DOM 節點
首屏一次渲染太多節點,會拖慢 JS 執行、Diff、Layout 和 Paint。
優化方式:
- 分頁。
- 虛擬列表。
- 折疊非首屏內容。
- 大型模組延後 mount。
- 不重要區塊延後到 idle 時間載入。
如果首屏就渲染一萬列,路由懶載入也救不了渲染成本。
2. 善用 v-if 與 v-show
不在首屏顯示的大型區塊,可以用 v-if 延後建立。
<HeavyPanel v-if="opened" />頻繁切換的小型區塊,用 v-show 可能更好。
延伸讀:v-if 與 v-show 的區別。
3. 降低響應式成本
大資料量不一定都需要深層響應式。
可考慮:
shallowRef。markRaw。- 避免把大型不可變資料整包 reactive。
- 拆分組件,縮小更新範圍。
v-memo避免不必要列表子樹更新。
<div
v-for="item in list"
:key="item.id"
v-memo="[item.id, item.selected]"
>
...
</div>4. 避免主執行緒長任務
首屏 JS 執行太久會讓頁面無法互動。
優化方向:
- 拆分重計算。
- 使用 Web Worker。
- 延後非必要初始化。
- 圖表、地圖、編輯器按需載入。
- 避免同步處理大量資料。
延伸讀:瀏覽器渲染管線與 DOM 操作成本。
六、 UI 感知優化
1. 骨架屏
骨架屏不能縮短真實載入時間,但能改善使用者感知。
適合資料請求不可避免的場景:
<template>
<ProductSkeleton v-if="loading" />
<ProductDetail v-else :product="product" />
</template>注意:骨架屏應該接近真實布局,避免資料回來後 CLS 過大。
2. 分階段渲染
先渲染核心內容,再載入次要內容:
首屏主內容
-> 推薦區
-> 評論
-> 圖表
-> 非必要互動模組這比「所有資料都回來才顯示頁面」更友好。
3. Loading 不等於優化
Loading 只能告訴使用者正在等,它不會讓頁面更快。
好的感知優化要配合:
- 穩定布局。
- 逐步顯示。
- 優先顯示有價值內容。
- 避免白屏。
七、 SSR / SSG / 預渲染
如果 SPA 首屏主要瓶頸是「JS 執行前沒有內容」,可以考慮改渲染模式。
1. SSR
Server 先渲染 HTML,瀏覽器先看到內容,再 hydrate。
適合:
- SEO 重要。
- 首屏內容可在 server 取得。
- 內容頁、商品頁、公開頁。
延伸讀:Vue SSR 的實現原理。
2. SSG
建置時產生靜態 HTML。
適合:
- 文件站。
- 部落格。
- 行銷頁。
- 內容變化不頻繁的頁面。
3. 預渲染
對部分路由預先生成 HTML,其他仍走 SPA。
適合少量公開路由首屏優化,但不想完整導入 SSR 的專案。
八、 追問題庫
Q1:首屏慢第一步做什麼?
先量測。用 Lighthouse、Performance、Network、bundle analyzer 確認瓶頸。
不要一上來就說 SSR。SSR 只能解決部分問題,如果 JS 過大、圖片過重、API 太慢,SSR 也不會自動全部解決。
Q2:路由懶載入一定能提升首屏嗎?
通常能減少首屏 bundle,但不一定改善所有首屏問題。
如果瓶頸是 API 慢、圖片大或首屏 DOM 太多,路由懶載入效果有限。
Q3:CDN 和 gzip/brotli 解決什麼問題?
它們主要解決資源傳輸速度。
但如果 bundle 本身太大,或 JS 執行太久,仍然需要分包、依賴治理與主執行緒優化。
Q4:骨架屏算不算性能優化?
算感知性能優化,不一定降低真實載入時間。
它能減少白屏焦慮,但不能取代資源體積與請求優化。
Q5:首屏一定要 SSR 嗎?
不一定。
後台管理系統、登入後工具、SEO 不重要的應用,通常 CSR + 分包 + 快取就夠。
公開內容頁、SEO 頁、商品頁,SSR/SSG 更值得考慮。
九、 實作題
題目:一個 Vue SPA 首屏 5 秒白屏,你怎麼排查?
可以這樣回答:
- 先用 Network 看 HTML、JS、CSS、圖片、API 的耗時與大小。
- 用 bundle analyzer 看主包是否過大,是否有大型依賴進入首屏。
- 檢查路由是否懶載入,重型組件是否按需載入。
- 看 API 是否串行、是否有不必要的首屏阻塞請求。
- 用 Performance 看 main thread 是否有 long task。
- 檢查首屏是否一次渲染過多 DOM 或列表。
- 檢查圖片、字體、CSS 是否阻塞或過大。
- 根據瓶頸選擇分包、快取、壓縮、骨架屏、SSR/SSG 等方案。
這個回答比直接背優化清單更像真實工程排查。
十、 資深視角
1. 首屏優化要分層
完整思路:
| 層級 | 問題 |
|---|---|
| 網路層 | TTFB、CDN、HTTP cache、壓縮 |
| 資源層 | JS/CSS/圖片/字體體積 |
| 執行層 | JS parse/execute、long task |
| 資料層 | API 串行、快取、預取 |
| 渲染層 | DOM 節點數、layout/paint、Diff 成本 |
| 架構層 | CSR、SSR、SSG、邊緣渲染 |
2. 不要讓優化破壞產品體驗
例如:
- 過度拆包可能造成路由切換卡頓。
- 過度 prefetch 可能搶首屏頻寬。
- 骨架屏如果布局不準,會造成 CLS。
- 快取如果策略錯誤,可能顯示舊資料。
性能優化不是越多越好,要看瓶頸與使用者路徑。
3. 面試時要能說出取捨
好答案不是一串名詞,而是:
- 我先量測。
- 我判斷瓶頸。
- 我選對應策略。
- 我知道副作用。
- 我會驗證結果。
總結
| 方向 | 常見手段 |
|---|---|
| 分析 | Lighthouse、Performance、Network、bundle analyzer |
| 分包 | 路由懶載入、組件懶載入、第三方庫按需載入 |
| 傳輸 | gzip/brotli、CDN、HTTP cache、資源 hash |
| 資源 | 圖片壓縮、WebP/AVIF、字體優化、CSS 精簡 |
| 資料 | 並行請求、快取、預取、非首屏延後 |
| 渲染 | 減少 DOM、虛擬列表、延後重型組件、降低響應式成本 |
| 感知 | 骨架屏、分階段渲染、避免白屏 |
| 架構 | SSR、SSG、預渲染 |
一句完整的面試回答可以是:
Vue SPA 首屏優化要先量測瓶頸,再對症處理。常見方向包括:路由懶載入和組件懶載入減少首屏 JS;Tree Shaking、按需引入、gzip/brotli、CDN 和長快取降低資源傳輸成本;圖片和字體做格式與載入優化;API 請求避免瀑布流並做快取;渲染上減少首屏 DOM、使用虛擬列表、延後重型組件;感知上用骨架屏和分階段渲染避免白屏。如果頁面 SEO 或首屏內容很重要,可以考慮 SSR、SSG 或預渲染,但要權衡伺服器成本與 hydration 複雜度。