繁體中文網站最容易被忽略的效能成本,往往不是第三方腳本,而是字型。

一套完整的 CJK 字型可能超過 10MB;如果把它整包送給每位訪客,手機使用者會先等字型下載,才看到穩定的文字。這篇整理 2026 年比較實用的做法:先用系統字體扛住大部分內文,再把真正需要品牌風格的字型做子集化。少載一點,咖啡就能早一點喝到。

先判斷:你真的需要 Web Font 嗎?

如果網站是內容型部落格、管理後台或文件站,最划算的選擇通常是系統字體優先。系統字體不需要額外下載,對 LCP、FCP 和行動流量都比較友善:

body {
  font-family:
    system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
    "PingFang TC", "Hiragino Sans GB", "Microsoft JhengHei",
    "Noto Sans CJK TC", sans-serif,
    "Apple Color Emoji", "Segoe UI Emoji";
}

macOS 與 iOS 通常會使用 PingFang TC,Windows 則多半 fallback 到 Microsoft JhengHei。如果你不需要完全一致的品牌字形,這一步可能比任何字型工具更有效。

三種字型策略怎麼選?

情境建議做法取捨
內容型部落格全站系統字體 + font-display: optional最快,但不同平台字形不完全一致
品牌官網或 Landing Page標題使用子集化 Web Font,內文用系統字兼顧美觀與速度,需維護字元清單
需要固定品牌字形的產品 UI自架 WOFF2 + 動態或靜態子集化控制力最高,但要加入 build 流程

不要為了「看起來專業」就讓整篇內文載入完整 Noto Sans TC。先把 Web Font 限制在 H1、H2 或少量 UI 元件,通常更容易得到可接受的效能與一致性。

為什麼繁中字型特別容易拖慢首屏?

中文字型需要涵蓋數千到數萬個漢字、標點與不同字重,完整檔案常達 10MB 以上。以一個實際靜態站案例來看,Google Fonts 曾依 Unicode 範圍送出 23 個請求、共約 1.78MB;改成依站內實際用字自架後,該案例收斂為 4 個請求。這是特定頁面與測試條件的案例,不是所有網站都會得到相同數字。

外部 CDN 通常還會多出 fonts.googleapis.comfonts.gstatic.com 的連線設定成本;但自架不代表一定更快,還要看 CDN、HTTP 版本、CSS 發現時機與實際字型分片。瀏覽器快取也可能受分區策略影響,跨站共用快取的優勢不能直接當成保證,因此「同網域自架」值得納入實測比較。

子集化到底在做什麼?

子集化就是從完整字型裡只留下網站真的會用到的字,再輸出較小的 WOFF2。

有兩種常見模式:

  1. 靜態子集化:掃描一頁內容,只保留這頁的字。檔案最小,適合單頁 Landing Page;但內容一改就要重跑。
  2. Unicode-range 分片:把字型切成許多小碎片,CSS 用 unicode-range 宣告每個碎片負責的範圍。瀏覽器會依頁面文字與範圍判斷需要哪些分片;一旦某分片匹配,通常會下載整個分片,而不是只下載其中一個字,適合內容持續增加的網站。

子集裡沒有的字不會自動變成方框。瀏覽器會沿著 font-family 堆疊找下一個可用字型;真正的代價是那個字可能和周圍文字有些風格差異。

自架子集化的實作流程

1. 保留 Latin 與站內實際 CJK 字元

掃描文章、模板與導覽文字,收集漢字、全形標點和英文數字。Latin 子集則建議沿用 Google Fonts 原本的字元範圍,避免英文、貨幣符號或常用標點突然 fallback。

2. 用工具產出 WOFF2

cn-font-split 適合把大字型切成 Unicode 分片;如果想依整個網站實際用字產出單一子集,也可以使用 subset-font,其底層是 HarfBuzz hb-subset 的 WASM 封裝。

概念上的流程如下:

掃描 .md / .njk
      ↓
收集 Latin + CJK 字元
      ↓
subset-font / cn-font-split
      ↓
輸出 .woff2
      ↓
CSS @font-face 掛載

3. 用 @font-face 控制載入行為

@font-face {
  font-family: "Noto Sans TC";
  src: url("/fonts/noto-sans-tc.woff2") format("woff2");
  font-weight: 400 700;
  font-style: normal;
  font-display: swap;
}

swap 會先顯示系統字,再替換成 Web Font,適合品牌標題;內容型網站若更在意避免文字跳動,可考慮 optional,讓載入不及時的訪客直接維持系統字。實際 block/swap 時間是瀏覽器依規格建議值實作,不是固定保證。

4. 把它接進 build 流程

新增文章或修改導覽列後,重新執行例如 pnpm run subset-fonts。原始字型可以快取,所以通常不需要每次重新下載;重點是讓「新增內容 → 重跑子集 → 測量效能」成為固定流程。

5. 移除不再需要的 CDN 標籤

改成自架後,檢查 <head> 裡的 Google Fonts preconnectpreload、stylesheet 與 noscript 標籤,避免新舊兩套字型同時載入。

常見錯誤與取捨

  • 直接上傳 OTF/TTF:檔案太大,應先輸出 WOFF2。
  • 只留下漢字、忘了 Latin:英文和數字可能換成不同風格,應保留常用 Latin 子集。
  • 把整個網站都套品牌字體:流量與載入成本會快速增加,先限制在標題或 UI。
  • 以為 fallback 就代表沒有問題:雖然不會 tofu,但罕見字的字形可能不一致,上線前要測試。
  • 只看檔案大小、不測實際體驗:請用 PageSpeed Insights 或自己的效能驗收流程觀察 LCP、FCP、CLS 與文字顯示是否改善。

推薦工具/資源

工具或做法適合誰特色
PageSpeed Insights所有站長檢查字型改動是否真的改善載入與 Core Web Vitals
cn-font-split需要 Unicode 分片的網站讓瀏覽器按需載入字型
subset-font想整合 Node.js build 流程的開發者將站內實際用字打包成 WOFF2
系統字體堆疊內容站與文件站不需下載,載入成本最低

結語:先少載入,再追求一致

  1. 內容站先用系統字體,不要預設整包載入 CJK Web Font。
  2. 需要品牌字形時,只把標題或 UI 子集化並自架 WOFF2。
  3. font-display、fallback 與實際 CWV 測量確認取捨,而不是只看理論檔案大小。

下一步很簡單:先用 PageSpeed Insights 記下改動前的數據,再選一個頁面做字型子集化,最後比較 LCP、CLS 和實際文字顯示。先測一頁就好,不必一口氣把整個網站的字型 pipeline 變成考古現場。