繁體中文網站最容易被忽略的效能成本,常常不是第三方腳本,而是字型。完整 CJK 字型可能很大;若每位訪客都先下載整包,手機網路就得先端上一杯「等待咖啡」,文字才慢慢穩定下來。
這篇會帶你判斷是否真的需要 Web Font、理解字型子集化與 unicode-range 的差別,最後用自架 WOFF2 的流程把載入成本降下來。重點不是追求某個神奇檔案大小,而是讓字型策略符合網站內容與實際測量結果。
先判斷:你真的需要 Web Font 嗎?
如果網站是內容型部落格、管理後台或文件站,最划算的起點通常是系統字體優先。系統字體不需要額外下載,少了一趟網路請求,也少了一個可能延遲文字顯示的變數:
body {
font-family:
system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
"PingFang TC", "Microsoft JhengHei", "Noto Sans CJK TC", sans-serif,
"Apple Color Emoji", "Segoe UI Emoji";
}macOS 與 iOS 常見的是 PingFang TC,Windows 常見的是 Microsoft JhengHei;實際結果仍會受作業系統、已安裝字型與瀏覽器影響。如果你不需要每台裝置都呈現完全相同的品牌字形,這一步可能比任何字型工具更有效。
三種字型策略怎麼選?
可以先用下面的方式縮小選擇:
| 情境 | 建議做法 | 主要取捨 |
|---|---|---|
| 內容型部落格 | 內文使用系統字體 | 載入成本低,但不同平台字形不完全一致 |
| 品牌官網或 Landing Page | 標題使用子集化 Web Font,內文使用系統字體 | 兼顧美觀與速度,但要維護字元清單 |
| 必須固定品牌字形的產品 UI | 自架 WOFF2,搭配靜態或分片子集化 | 控制力最高,也需要加入 build 流程 |
不要只因為「看起來專業」,就讓整篇內文載入完整的 Noto Sans TC。先把 Web Font 限制在 H1、H2 或少量 UI 元件,通常更容易取得可接受的速度與一致性。字型不是越多越有文化,請求數才不會因此變成詩。
為什麼繁中字型特別容易拖慢首屏?
中文字型需要涵蓋大量漢字、標點與不同字重,因此完整檔案可能遠大於 Latin 字型。一個網站若同時載入多個 CJK 字型族和多個字重,下載量很快就會累積;若使用外部服務,還要考慮連線、CSS 發現時機與字型檔本身的請求。
外部 CDN 不一定比較慢,自架也不保證比較快。實際結果會受到 CDN 位置、HTTP 版本、快取策略、CSS 何時被瀏覽器發現,以及頁面到底匹配到哪些字型分片影響。跨網站共用快取也不能當成必然優勢,所以應該把「外部服務」和「自架」都放進同一套實測裡比較。
字型子集化到底在做什麼?
子集化就是從完整字型中只保留網站需要的字元,再輸出較小的字型檔。HarfBuzz 的 hb-subset 文件也把它描述為縮減字型涵蓋的 codepoint,並移除不再需要的資料。
常見做法有兩種:
- 靜態子集化:掃描指定頁面或整個網站,只保留目前用到的字元。檔案通常更小,適合內容固定的 Landing Page;但內容更新後必須重跑。
- Unicode 分片:把字型切成多個檔案,在 CSS 的每組
@font-face中用unicode-range指定負責的字元範圍。瀏覽器會根據頁面文字判斷要不要載入某個分片,但匹配後下載的是整個分片,不是單獨一個字。
這裡有個容易誤會的地方:WOFF2 格式和瀏覽器不會自動替你切檔。分片需要多組 @font-face、多個 WOFF2 網址,以及對應的 unicode-range。只有一個完整 WOFF2 和一條 @font-face,瀏覽器就只會把它當成一個檔案處理。
Google Fonts 會替你產生包含 unicode-range 的 CSS,某些字型也會依字元或語系切成多個檔案;你只放入 stylesheet 連結,就能使用它預先準備好的分片。自架則要自行產生分片與 CSS,例如使用 cn-font-split。
本站曾以四種字型載入約 23 個 WOFF2 請求、總計約 1.78MB 作為改版前觀測值;後來改用自架子集,並讓內文回到系統字體,目前實際使用的 Web Font 減少到兩個檔案。這是本站某次測量,不是所有網站都會得到相同數字;分片數量減少,也不代表每個頁面一定少下載同樣多的位元組。
子集缺少的字也不會自動變成缺字方框。瀏覽器會沿著 font-family 堆疊尋找下一個可用字型,代價是罕見字可能和周圍文字有些風格差異。因此,子集化完成後要測試特殊人名、專有名詞、全形標點與程式碼符號。
自架子集化的實作流程
1. 收集真正會出現的字元
掃描文章、模板、導覽列、按鈕和錯誤訊息,收集漢字、全形標點、Latin 字母、數字與常用符號。不要只掃一篇文章,否則下一篇新增一個罕見字時,字型就會突然「失憶」。
Latin 部分可以參考字型服務提供的範圍,但仍要依網站實際內容確認,避免英文、貨幣符號或程式碼標點意外 fallback。若標題和內文使用不同字型,也應分別收集,避免把所有文章文字都塞進只套用於標題的字型。
2. 用工具產出 WOFF2
cn-font-split 適合把 CJK 字型切成可按範圍載入的分片;如果想依網站實際用字產出單一子集,可以使用 subset-font。這個 Node.js 套件以 HarfBuzz 的 subset 功能為基礎,會輸出指定格式的字型檔。
概念流程如下:
掃描 Markdown、模板與導覽文字
↓
收集 Latin、CJK 與符號
↓
subset-font 或 cn-font-split
↓
輸出 WOFF2 與必要的 CSS
↓
用瀏覽器與效能工具驗證如果專案已經有腳本,讓它成為固定的 build 步驟會比手動操作可靠。例如本站可在新增內容後執行:
pnpm run subset-fonts
pnpm run build3. 用 @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;
}接著在 HTML 中把它套用到真正需要的元素,而不是只宣告卻全站使用:
<h1 class="hero-title">網站標題</h1>font-display: swap 會先顯示 fallback 字型,字型載入完成後再替換,適合需要優先看見文字的標題。optional 則讓瀏覽器在載入不及時時更傾向維持 fallback;它可以降低字型替換造成版面變化的機會,但不是「完全不會 CLS」的保證。實際顯示時間仍由瀏覽器實作與網路狀況決定。
4. 把子集化接進 build 流程
新增文章或修改導覽列後,重新執行字型腳本,再進行 production build。原始字型可以放在本機快取,避免每次重跑都重新下載;真正重要的是讓「新增內容 → 重新產生子集 → 測量效能」成為固定流程。
5. 移除不再需要的 CDN 標籤
改成自架後,檢查 <head> 裡的 Google Fonts preconnect、preload、stylesheet 和 noscript 標籤。新舊兩套設定同時留下來,等於請瀏覽器喝兩杯咖啡再做同一件事——熱鬧,但沒有比較快。
常見錯誤與取捨
- 直接上傳 OTF 或 TTF:Web Font 通常優先使用 WOFF2,因為壓縮效率較好且現代瀏覽器支援廣;仍要依你的瀏覽器支援範圍決定是否提供其他格式。
- 以為分片等於只下載一個字:
unicode-range只負責判斷某個分片是否匹配;匹配後通常會下載整個分片。 - 只留下漢字、忘了 Latin 和符號:英文、數字、括號或程式碼符號可能改用不同字型,應把實際需要的字元一起納入。
- 把整個網站都套品牌字體:先從標題或少量 UI 開始,否則字型下載量和替換風險都會快速增加。
- 忽略子集缺字:fallback 能避免缺字方框,卻不會讓不同字型的字形風格自動一致;上線前要測試罕見字、專有名詞與標點。
- 只看檔案大小、不測實際體驗:請用 PageSpeed Insights 或自己的效能驗收流程,觀察 LCP、CLS、文字是否及時出現,以及 Network 面板實際下載了哪些字型。
結語:先少載入,再追求一致
字型最佳化可以先記住三件事:
- 內容站先用系統字體,不要預設整包載入 CJK Web Font。
- 需要品牌字形時,只把標題或 UI 子集化,並依需求選擇單一子集或 Unicode 分片。
- 用
font-display、fallback 和實際 Core Web Vitals 測量確認取捨,不要只看理論檔案大小。
下一步很簡單:先記下改動前的數據,挑一個頁面做子集化,再比較 LCP、CLS、字型請求數和實際文字顯示。先測一頁就好,不必一口氣把整個網站的字型 pipeline 變成考古現場。測完再來一杯,這次是慶功。
參考資料
- MDN:
@font-face:查閱src、font-display、unicode-range與 Web Font 格式。 - MDN:Web fonts:說明 WOFF2、fallback、子集化與字型實際載入時機。
- Google Fonts CSS API:說明 Google Fonts CSS、
text參數與字型請求格式。 - HarfBuzz:
hb-subset:說明字型子集化如何縮減 codepoint 涵蓋範圍與不必要資料。 subset-fontGitHub:Node.js 字型子集化套件的原始碼與使用資訊。cn-font-splitGitHub:CJK 字型分片工具的功能與文件入口。

