WordPress 中文字型優化——子集化與分包實戰

把一套漂亮的繁體中文字型掛上 WordPress,往往是速度報告變紅的開始。一個完整的思源黑體 TTF 動輒 15 MB 以上,華為 HarmonyOS Sans 的完整字重也接近 8 MB,而英文字型整套通常不到 200 KB。這個差距來自漢字數量本身:常用正體中文就有四千八百多字,全字集破萬,光是把這包檔案丟給瀏覽器下載,行動網路使用者就得多等好幾秒,LCP(最大內容繪製)跟著被拖垮。

WordPress 中文字型優化的核心,是讓瀏覽器只下載「這一頁真正用到的字」,而不是整套字庫。達成這件事有三條技術路線:字型子集化(subset)、字型分包(split,搭配 unicode-range),以及可變字型(variable font)。三者解決的問題不同,適用情境也不同,混為一談就會選錯工具。這篇會把三條路線拆開講清楚,並給出在 WordPress 上實際落地的步驟、踩雷點與量測方法。

中文網頁字型為什麼這麼肥,肥在哪裡

問題的根源是字數,不是檔案格式沒壓縮。漢字是表意文字,每一個字都是獨立的字形(glyph),一套涵蓋台灣常用字、簡體、日文漢字的全字集字型,字形數量是拉丁字母的數十倍到上百倍。即使用最好的壓縮,整包仍然是 MB 等級。

一張網頁的載入順序也讓字型雪上加霜。瀏覽器要先解析 HTML、CSS、JavaScript,發現 CSS 裡 @font-face 指到的字型檔之後才會去下載它。在實測的載入瀑布圖裡,一個 8 MB 的中文字型可能單獨吃掉九秒以上的下載時間,這段期間文字要嘛看不見(FOIT,不可見文字閃爍),要嘛先用系統字撐著、字型到了再換(FOUT,無樣式文字閃爍)。對首次造訪、快取是空的訪客來說,這就是直接的跳出風險。

所以優化方向很明確:砍掉不會用到的字形,並把剩下的用最有效率的格式與載入策略送出去。下面三種策略都圍繞這個目標,差別在於「怎麼砍」與「怎麼送」。

字型子集化是什麼,什麼情況下最划算

字型子集化(subsetting)是從一套大字型裡,只取出你網站會用到的字,產生一份精簡字型檔。沒被收進子集的字,瀏覽器會退回系統字型顯示(在台灣通常是新細明體或微軟正黑體這類襯線/黑體系統字)。

子集化最適合的情境是用字範圍可預期、且相對固定的網站:標題用特殊字型、導覽列與按鈕文案變動不大、或整站走特定設計字體但內容字彙集中。實測上,把華為 HarmonyOS Sans 從接近 8 MB 的 TTF 子集化後,WOFF 版本降到約 1 MB,再轉成 WOFF2 約 879 KB,等於砍掉約九成體積。

子集化要先決定收哪些字。常見做法是準備一份字表:

  • 台灣常用字:教育部常用字約四千八百多字,是內文的安全底線。
  • 次常用字:文謅謅或專業內容會用到表外字(例如「謅」「餚」「笅」這類),漏收就會在頁面上看到那幾個字明顯跟其他字長得不一樣,因為它們退回了系統字。
  • 數字、英文與標點符號:別忘了半形與全形標點,否則標點會錯字型。
  • 要不要收簡體與日文漢字:純繁中網站可以整批剔除,這是省最多體積的一刀。

決定字表後,用工具把字型「剪」成子集。早年常見的是 GUI 工具,例如 Subset FontMaker 搭配 WOFF Converter、或 Fontmin,把 TTF 拖進去、貼上要保留的字、輸出 WOFF2。這類工具上手快,但每次內容新增表外字就要重做一次,維護成本高。

如果你願意碰命令列,Python 的 fonttools(pyftsubset 指令)更適合自動化:給它一份字型與一份字表,就能輸出指定字集的 WOFF2,還能控制要不要保留 OpenType 字距特性。把它接進部署流程,每次發布前自動重跑,就不會漏字。

子集化的代價是收字範圍寫死。一旦使用者輸入的內容超出子集(例如留言、會員暱稱、動態生成的文章標題),超出的字就會掉回系統字型。所以子集化適合可控的設計性字型,不適合「整站每一個字都要是這套字」又「內容會無限增長」的場景。後者要看下一個策略。

字型分包與 unicode-range 怎麼讓字型按需載入

字型分包(splitting)把一套完整字型切成數十到上百個小檔,再用 CSS 的 unicode-range 告訴瀏覽器「哪個字落在哪一包」,瀏覽器只下載當前頁面實際出現的字所在的那幾包。這是 Google Fonts 處理中日韓字型的核心手法,也是內容會持續成長的網站該選的路線。

它跟子集化的根本差別在於:子集化是「永久丟掉沒收的字」,分包是「字都還在,只是拆成很多小包按需取用」。所以分包不會有掉字問題,代價是總流量比子集化大(因為理論上整套字都可能被下載到),需要靠 CDN 與 HTTP/2、HTTP/3 的多檔並發來吸收這個成本。

unicode-range 的寫法是在每個 @font-face 區塊標明這一包涵蓋的 Unicode 範圍:

@font-face {
  font-family: "MySplitFont";
  src: url("/fonts/mysplit.1.woff2") format("woff2");
  font-display: swap;
  unicode-range: U+4E00-4FFF;
}
@font-face {
  font-family: "MySplitFont";
  src: url("/fonts/mysplit.2.woff2") format("woff2");
  font-display: swap;
  unicode-range: U+5000-51FF;
}

同一個 font-family 名稱、不同的 unicode-range 與檔案,瀏覽器會自己挑頁面上出現的字去對應的包下載。

分包的切法會直接影響效果。一個被驗證有效的策略是:把最高頻的約兩千字放進第一包,次高頻的約一千字放第二包,剩下的字按 Unicode 編碼平均切成上百個等大小的包。這樣多數頁面只會命中前幾包加少數冷門包,實測可比下載整套字型少傳輸約八成八的位元組。每包大小建議切在 70 KB 上下,在 HTTP/2 並發下單包載入約一秒半、整頁字型完整載入大致能壓在兩秒內。

手動切分包幾乎不可行,要靠工具。線上服務「中文网字计划」可以把字型上傳後自動分包,輸出 WOFF2 與對應的 CSS;想整進自動化流程的話,開源的 cn-font-split 支援多執行緒切割 TTF、OTF、WOFF2,能精細控制單包大小,且對 CJK 與少數民族文字、阿拉伯文都有處理,適合放進建置腳本。

WordPress 上要怎麼把子集化或分包字型真正裝上去

前面是通用的字型處理,這一段講 WordPress 端的落地,這也是多數教學講得最含糊、實際最容易卡關的環節。流程分成三步:上傳字型檔、寫入 @font-face、覆寫佈景主題的 font-family

字型檔上傳要先解決 WordPress 的 MIME 限制

WordPress 預設媒體庫不允許上傳 .woff2,分包輸出的 .json 之類檔案在新版(6.6 之後)也會被擋。有兩個解法。第一是裝一個 MIME 類型外掛(例如 WP Add Mime Types),在設定裡補上對應規則:

woff2 = font/woff2
css = text/css

第二是不要走媒體庫,直接用 FTP 或主機檔案管理員,把字型檔放到佈景主題目錄下自建的 fonts/ 資料夾。第二種更乾淨,因為字型屬於佈景主題資產,跟著主題走比較好管理,也避免媒體庫被一堆分包小檔塞爆。

字型檔放哪裡會影響載入速度。若有 CDN,最好讓字型檔走 CDN 路徑,CDN 的傳輸效率通常勝過共享主機。沒有 CDN 又用分包的話,幾十上百個小檔的請求會讓共享主機的負擔變明顯,這時子集化(單一檔案)反而比分包更務實。

@font-face 與 preload 寫在哪裡

@font-face 宣告與 font-display 設定,放進「外觀 → 自訂 → 額外的 CSS」最簡單,但更穩的是放進子佈景主題的樣式表,避免主題更新時被覆蓋。分包工具產生的 CSS 檔可以整份上傳,再用一行 @import 引入,或用能插入 <head> 的程式碼片段外掛掛上 <link rel="stylesheet">

關鍵字型建議再加 preload,讓瀏覽器提早抓。在 <head> 加:

<link rel="preload" href="/wp-content/themes/your-child/fonts/main.woff2" as="font" type="font/woff2" crossorigin>

crossorigin 屬性對字型是必填,漏了瀏覽器會重抓一次。preload 要克制,一般首頁預載一到兩個最關鍵的字型檔就夠,全部都 preload 等於沒有優先序。分包字型通常不適合 preload,因為你事先不知道哪幾包會被命中。

font-family 覆寫要對準佈景主題的寫法

裝好字型後,得讓佈景主題實際用它。這一步沒有萬用解,因為每個主題定義字型家族的方式不同。

現代區塊主題(如 Blocksy)常把字型存在 CSS 變數裡,例如 --theme-font-family,這時最乾淨的覆寫是改那個變數:

:root {
  --theme-font-family: "MySubsetFont", sans-serif;
}

舊式主題(如 Astra)多半直接寫在 body 與表單元素上,覆寫 body 多半就夠:

body {
  font-family: "MySubsetFont", sans-serif;
}

但要注意文章標題、側邊欄標題、按鈕這些元素常有獨立的 font-family 宣告,光改 body 不會生效,得用瀏覽器開發者工具(F12)選到該元素、在樣式面板搜 font-family,找出主題用的選擇器再個別覆寫。font-family 後面務必補上系統字型作後備,字型還沒載入或掉字時才有東西頂著。

WordPress 6.5 起內建的字型庫(Font Library)能下載並在本地代管字型,對區塊主題來說是最省事的自架方式,但它管的是 Google Fonts 那類整套字型,不會幫你做中文子集化或分包,省體積這件事仍得靠前面的工具自己處理。

可變字型對中文網站到底有沒有用

可變字型(variable font)把同一字體家族的多種變化收進單一檔案,透過參數(軸)即時調整字形,常見的軸有字重(wght)、字寬(wdth)、視覺尺寸(opsz)等。CSS 用 font-variation-settings 或直接用 font-weightfont-stretch 控制:

.heading {
  font-family: "MyVariableFont";
  font-variation-settings: "wght" 600;
}

可變字型省體積的前提是你真的用到多個字重或字寬。如果一個網站本來就要載入細體、一般、中黑、粗體四個獨立字重檔,改用一個可變字型檔,總體積通常會比四個分開的檔小,這時划算。但如果你整站只用單一字重,可變字型反而可能比單一字重的靜態子集更大,因為它額外背了描述軸變化的資料。

對中文來說還有兩個現實限制。第一,中文可變字型數量稀少,繁體可選的更少,目前能找到的多是黑體系列(例如文鼎晶熙黑、思源系字型的可變版、昭源字體),宋體系的可變字型選擇有限。第二,可變字型同樣是 MB 等級的大檔,它不解決「字太多」這個本質問題,省的是「字重太多」。所以實務上正確的組合是:先用子集化或分包砍掉用不到的字形,再視需求決定要不要用可變字型來收斂字重,兩者是疊加而非互斥。

結論很直接:多字重的設計型網站,可變字型值得評估;單字重內文站,把力氣放在子集化或分包,效益遠大於換可變字型。

font-display 與版面位移該怎麼設定才不扣分

字型檔變小只解決了一半,另一半是「字型載入過程中頁面看起來如何」,這直接關係到 CLS(累積版面位移)這項 Core Web Vitals 指標。

font-display 控制字型載入期間的顯示行為,常用三種值:

  • swap:先用後備系統字顯示文字,自訂字型載入完成後換上。文字永遠看得見,代價是換字瞬間可能有視覺跳動。多數中文內容站的安全預設。
  • optional:給字型很短的載入時間,逾時就這次直接用系統字、不換,把字型留到下次快取命中再用。對 CLS 最友善,適合字型只是錦上添花、不是品牌識別核心的情況。
  • fallback:介於兩者之間,極短的隱藏期後用系統字,短時間內載到才換。

純就速度報告而言,swap 保證沒有 FOIT、文字一定可見,是最常見的選擇;但 swap 的換字會造成版面跳動,因為系統後備字跟自訂字的字寬、行高不一致。要壓低這個跳動,可以在後備 @font-face 上用 size-adjustascent-overridedescent-override 這些屬性,讓後備字的度量盡量貼近自訂字,換字時位移就小。這是多數中文字型教學沒講到、卻最影響 CLS 分數的一步。

怎麼確認優化真的有效

優化完一定要量測,不能只看「感覺變快了」。用 PageSpeed Insights 或 Lighthouse 跑優化前後的對照,重點看三個地方。

第一是 LCP,字型若卡住首屏主要文字的繪製,子集化或分包後 LCP 應明顯下降。第二是瀏覽器開發者工具的 Network 面板,過濾字型檔,確認單檔體積、實際下載了幾個分包、有沒有發生 404(路徑或 MIME 設錯的典型症狀)。第三是 CLS,觀察字型換上時版面有沒有大幅跳動,跳動明顯就回頭調 font-display 與後備字度量。

一個務實的驗收標準是:行動裝置首頁的字型相關下載總量壓在數百 KB 以內、字型完整載入控制在兩秒上下、CLS 維持在綠燈區間。達到這個程度,中文字型就從速度的拖油瓶變回設計的加分項。

回到一開始的選擇。用字可控、要省到極致就走子集化;內容會長、不能掉字就走分包加 unicode-range;需要多字重才考慮可變字型疊上去。WordPress 端記得先解 MIME 限制、@font-face 與覆寫 CSS 放進子佈景主題、關鍵字型補 preload,最後用 PageSpeed 與開發者工具收尾驗證。把這條流程跑完一次,下次換字型就只是重跑工具、替換檔案的例行公事。

相關文章
標籤: 網頁效能, 字型子集化, WOFF2, unicode-range, 可變字型