跳到主要內容

效能:先量測,再最佳化

該看哪三個指標、首屏預算怎麼訂、常見的自傷行為,以及不要提前最佳化的具體理由。

約 5 分鐘 · performance.md

1. 三個指標

指標 意思 目標
LCP 最大內容繪製 —— 主視覺或標題出現的時間 < 2.5s
INP 互動到下一次繪製 —— 點下去多久才有反應 < 200ms
CLS 累積版面位移 —— 東西會不會亂跳 < 0.1

其他數字(bundle 大小、請求數)是手段,這三個才是使用者的體感。

量測:Lighthouse 看實驗室數據,真實使用者資料用 web-vitals 套件回報。開發機上的數字沒有意義 —— 你的網路太快、裝置太好。

2. 首屏預算

項目 上限
JS(gzip) 120KB
CSS 40KB
圖片 250KB
字型 2 個家族,各 ≤ 2 字重
第三方腳本 越少越好,能不要就不要

超過要有人簽字。沒有預算的效能討論會變成「感覺有點慢」然後不了了之。

3. 改善 LCP

  1. 首屏圖片不要 lazy,加 fetchpriority="high"
  2. 字型加 preconnect,用 display: swap
  3. 首屏內容用 SSR,不要等 JS 才渲染
  4. 關鍵 CSS 內聯或提早載入
  5. 不要讓第三方腳本擋住渲染async / defer

最常見的 LCP 殺手是「首屏主視覺加了 loading="lazy"」—— 出於好意,效果相反。

4. 改善 INP

  • 把長任務切開(scheduler.yield()setTimeout 0
  • 捲動與滑鼠監聽用 { passive: true }
  • 高頻更新(串流輸出、拖曳)用 requestAnimationFrame 節流
  • 不要在事件處理裡做同步的大量計算
export function rafThrottle<T extends (...args: never[]) => void>(fn: T) {
  let frame: number | null = null;
  let lastArgs: Parameters<T> | null = null;

  return (...args: Parameters<T>) => {
    lastArgs = args;
    if (frame !== null) return;
    frame = requestAnimationFrame(() => {
      frame = null;
      if (lastArgs) fn(...lastArgs);
    });
  };
}

模型每秒可以吐幾百個 token,但螢幕一秒只更新 60 次。不節流的話,你在為沒人看得到的畫面燒 CPU。

5. 改善 CLS

layout-and-overlap 第 6 節。摘要:

  • 圖片給尺寸
  • 字型用接近的 fallback
  • 骨架屏尺寸要對
  • scrollbar-gutter: stable
  • 動態插入的東西要預留空間

6. 減少 JS

先刪,再優化。

npx vite-bundle-visualizer      # 看誰最大
npx knip                        # 找沒用到的檔案與依賴
npx bundle-phobia <package>     # 加之前先看大小

常見的肥肉:

套件 大小 替代
moment ~70KB Intl.DateTimeFormat(0KB)
lodash(整包) ~70KB 只 import 需要的,或自己寫五行
axios ~15KB fetch(0KB)
圖示套件(整包) 100KB+ 只 inline 你用到的那幾個 SVG
UI 套件 100KB+ 這套設計系統就是純 CSS

加一個依賴之前先問:這五行我自己寫要多久?

7. 不要提前最佳化

// 沒量測就加 memo,只是讓程式碼變難讀
const value = useMemo(() => a + b, [a, b]);
const handler = useCallback(() => setOpen(true), []);

useMemo / useCallback 本身也有成本(比較依賴、佔記憶體)。對於便宜的計算,它比直接算還慢。

加 memo 的正當理由只有一個:你用 profiler 量到它慢。

同樣的道理適用於:虛擬捲動(先確認清單真的長)、Web Worker(先確認計算真的重)、快取層(先確認真的重複算)。

8. Cloudflare Workers 的特性

  • 冷啟動幾乎為零,不需要為此設計
  • CPU 時間有限(免費方案 10ms,付費 30s),重運算要丟去別的地方
  • 靜態資產走 assets binding,不進 worker,最快
  • 能預先產生就預先產生:本站的 /themes.css 是純計算的結果,所以 build 時就變成靜態檔,執行期零成本

9. 量測流程

  1. 先在節流的網路與 CPU 下錄一次(DevTools: Slow 4G + 4x CPU slowdown)
  2. 找出最大的那一個問題
  3. 只修那一個
  4. 再量一次
  5. 重複

一次改五個地方,你不會知道哪一個有效。

10. 檢查清單

  • 有量過 LCP / INP / CLS,不是憑感覺
  • 首屏圖片沒有 lazy,有 fetchpriority="high"
  • 首屏 JS < 120KB gzip
  • 沒有 moment、沒有整包 lodash、沒有整包圖示庫
  • 高頻更新有節流
  • 捲動監聽是 passive
  • memo 都是量測後才加的
  • 能預先產生的東西沒有留在執行期算

顯示設定

這裡改的每一項,會即時套用到站上所有預覽。

風格

密度

圓角

動態

系統層級的「減少動態效果」永遠優先於這裡的設定。

語言