Google Fonts 國內載入慢怎麼辦:自託管與中文字型子集化
Google Fonts 國內載入慢,原因在於頁面要先連上 fonts.googleapis.com 拿 CSS,再連 fonts.gstatic.com 拿字型檔案,這兩個域名在不少國內網路下連線慢甚至失敗,而那段 CSS 預設會阻塞渲染。解決辦法是把字型檔案下載下來和站點放在一起(自託管),再配合子集化和 font-display。下文依次講原因、遷移步驟、中文字型處理和上線驗證。
外鏈字型為什麼會拖住首屏?
一條常見的引用寫法是:
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap" rel="stylesheet">
瀏覽器處理它要經過這幾步:
- 解析到這個
<link rel="stylesheet">。外部樣式表預設是渲染阻塞資源,CSS 沒回來之前頁面不會首次繪製。 - 為
fonts.googleapis.com做 DNS 解析、TCP 連線和 TLS 握手,然後下載 CSS。 - CSS 裡的
@font-face指向fonts.gstatic.com,又是一個新域名,再做一輪 DNS、TCP、TLS。 - 字型檔案下載完,文字才用目標字型渲染。
第 2、3 步是兩次“首次連線”,和請求本身多大關係不大。網路好的時候各幾十毫秒,網路差的時候可能是幾秒,或者直接超時。第 1 步決定了最壞情況:CSS 卡住,整頁白屏。display=swap 只管字型檔案(第 4 步),管不了阻塞渲染的那份 CSS。
還有一個常被引用的舊理由已經不成立:“用公共字型 CDN 可以和別的網站共享快取”。主流瀏覽器都已按站點分割槽快取(Chrome 從 86 版開始),A 站快取的字型,B 站用不上。對國內訪客來說,外鏈 Google Fonts 已經沒有快取上的收益,只剩連線成本。
怎麼測出這兩個域名在你那裡有多慢?
可以用下面三種方法實測:
- 開啟 cdnping 線上測速,看公共前端 CDN 的“首次連線”一欄。它包含 DNS 和 TLS 握手,正好對應上面第 2、3 步的成本;“往返”是連線複用後 3 次請求的中位數。兩者差得越多,說明“多一個域名”在你的網路裡越貴。
- 開啟自己的頁面,DevTools → Network,勾選 Disable cache,篩選
fonts.g,看每個請求的 Timing 裡 DNS Lookup、Initial connection、SSL 三段。 - 命令列可以用 curl 拆開看:
curl -o /dev/null -s -w 'dns=%{time_namelookup} tcp=%{time_connect} tls=%{time_appconnect} total=%{time_total}\n' \
https://fonts.gstatic.com/
本文不給具體數字:結果取決於運營商、地區和時段,別人測的數字對你參考價值有限。cdnping 自 2026 年 10 月 17 日開始採集,歷史還很短,測速口徑見 資料方法。如果整站在國內都慢,而不只是字型,可以先看 靜態網站國內訪問慢怎麼診斷,DNS 層面的異常可以參考 DNS 汙染和 DNS 劫持怎麼判斷。
自託管 Google Fonts 的步驟
Google Fonts 上的字型大多采用 SIL Open Font License 或 Apache License,允許自行分發。自託管前確認一下具體字型的許可,並把 LICENSE 檔案和字型放在一起。
第 1 步:列出頁面實際用到的字型和字重
grep -rn "fonts.googleapis\|fonts.gstatic" src/ public/ index.html
把每個 family 和 wght 記下來。很多專案引用了 300、400、500、700 四個字重,實際只用到 400 和 700,刪掉不用的字重就能少幾個請求。
第 2 步:拿到 woff2 檔案
有三種方式,從省事到手動排列。
方式 A:用 fontsource 的 npm 包(適合有構建流程的專案)
fontsource 把 Google Fonts 打包成 npm 包,按字重和字元子集拆好了檔案,@font-face 也寫好了:
npm i @fontsource/inter # 靜態字重
# 或可變字型版本
npm i @fontsource-variable/inter
// main.js / main.ts
import '@fontsource/inter/400.css';
import '@fontsource/inter/700.css';
body { font-family: 'Inter', system-ui, sans-serif; }
構建後字型檔案會進入 dist/assets/,檔名帶 hash。如果 npm install 本身就慢,先看 npm 映象源怎麼設定。
方式 B:構建時自動下載
Vite 專案可以用 vite-plugin-webfont-dl 這類外掛,構建時把 HTML 裡的 Google Fonts 連結下載下來、改寫成本地路徑,原始碼裡的 <link> 不用動。注意構建機必須能訪問 Google 的這兩個域名,CI 跑在海外一般沒問題。
方式 C:手動下載
Google Fonts 的 CSS 介面會按 User-Agent 返回不同格式,帶上現代瀏覽器的 UA 才會給 woff2:
UA='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36'
curl -sA "$UA" 'https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap' -o inter.css
grep -o 'https://fonts.gstatic.com/[^)]*' inter.css | while read u; do
curl -sO "$u"
done
然後把 inter.css 裡的 URL 改成你自己的路徑,比如 /fonts/xxx.woff2。
第 3 步:寫 @font-face,保留 unicode-range
Google 返回的 CSS 已經按 latin、latin-ext、cyrillic 等字符集拆成了多段,每段帶 unicode-range。瀏覽器只下載頁面上真正出現的字元所在的那一段。手動搬運時不要把這些段合併,只刪掉確定用不到的(比如純中文站可以刪 cyrillic、greek、vietnamese):
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url('/fonts/inter-latin-400.woff2') format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
只寫 woff2 就夠了,當前主流瀏覽器都支援,不必再帶 woff、ttf、eot。
第 4 步:preload 首屏字型,但別貪多
<link rel="preload" href="/fonts/inter-latin-400.woff2" as="font" type="font/woff2" crossorigin>
兩個細節:crossorigin 必須寫,即使是同域,否則預載入的檔案不會被複用,會下載兩次;只預載入首屏正文用的那一個檔案。把所有字重都 preload 會和首屏圖片、指令碼搶頻寬,反而更慢。
font-display 選哪個?
| 取值 | 行為 | 適合 |
|---|---|---|
swap | 立即用後備字型顯示,字型到了再替換 | 正文、品牌字型,最常用 |
fallback | 約 100ms 隱形期,約 3 秒內到達才替換,否則本次不換 | 正文字型,想減少晚到時的跳動 |
optional | 約 100ms 內沒到就不用,留給下次訪問(已快取) | 字型可有可無、對佈局穩定要求高 |
block | 最長約 3 秒隱形,再換後備 | 圖示字型,不建議用於正文 |
swap 的代價是字型替換時的佈局偏移(CLS)。可以用 size-adjust、ascent-override 讓後備字型的度量接近目標字型,減小跳動:
@font-face {
font-family: 'Inter Fallback';
src: local('Arial');
size-adjust: 107%;
ascent-override: 90%;
}
body { font-family: 'Inter', 'Inter Fallback', system-ui, sans-serif; }
上面的百分比只是示例,具體值需要按字型計算。構建工具裡有自動生成後備度量的外掛,例如 unjs 的 fontaine,可以省去手算。
中文字型怎麼辦?
拉丁字型一個字重通常幾十 KB,中文字型要覆蓋幾千到幾萬個字,完整檔案常以 MB 計,不處理就直接引用會明顯拖慢首屏。按需求從輕到重有三種做法。
能用系統字型就用系統字型
大多數正文場景,系統自帶的中文字型已經夠用,零下載:
body {
font-family: system-ui, -apple-system, 'PingFang SC', 'Hiragino Sans GB',
'Microsoft YaHei', 'Noto Sans CJK SC', 'Source Han Sans SC', sans-serif;
}
英文部分用自託管的拉丁字型,中文交給系統字型,額外下載只有拉丁字型那幾個檔案。
文字固定:按實際用到的字子集化
Logo、標題、按鈕這類文字固定的場景,用 fonttools 只保留用到的字:
pip install fonttools brotli
# chars.txt 裡放頁面實際用到的所有字元
pyftsubset NotoSansSC-Bold.otf \
--text-file=chars.txt \
--flavor=woff2 \
--layout-features='*' \
--output-file=title-sc.woff2
幾十個字的子集通常只有幾 KB 到十幾 KB。缺點是文案一改就要重新生成,最好放進構建指令碼:從模板裡提取字元,自動跑一遍。
文字不固定:按 unicode-range 切片
部落格、文件這類內容隨時變化的站點,做法和 Google Fonts 處理中日韓字型一樣:把整套字型切成上百個小檔案,每個帶自己的 unicode-range,瀏覽器只下載頁面上出現的字所在的分片。
- 直接用 fontsource 的中文包,例如
@fontsource/noto-sans-sc,它沿用了 Google 的切片方式,裝上就能用。 - 想用其他中文字型,可以用專門的切片工具(例如開源的 cn-font-split)自己切,輸出 woff2 分片和對應的 CSS。
- 自己規劃切片時,常用的思路是把《通用規範漢字表》一級字表的 3500 個常用字按使用頻率分到前面幾片,生僻字放後面,這樣一篇普通文章只觸發少數幾個分片。
切片多了以後請求數也多,所以站點最好開著 HTTP/2 或 HTTP/3,讓這些請求複用同一個連線。
自託管後的快取和響應頭
字型自託管後,快取頭決定回訪是否還要重新下載。構建產物檔名帶 hash 時,可以放心設長快取:
location ~* \.woff2$ {
types { font/woff2 woff2; }
add_header Cache-Control "public, max-age=31536000, immutable";
add_header Access-Control-Allow-Origin "*";
}
Content-Type用font/woff2。- 檔名不帶 hash(比如方式 C 手動放到
/fonts/的檔案)就不要加immutable,換字型時改檔名或加版本引數。各指令的含義見 Cache-Control 快取頭怎麼配。 - 字型放在和頁面不同的域名上(比如單獨的靜態資源域名),字型請求屬於跨域請求,必須返回
Access-Control-Allow-Origin,否則瀏覽器會拒絕使用。
如果站點本身放在海外託管、國內訪問就慢,字型跟著站點走也快不起來。這時要看整站的分發方案:選公共 CDN 可以先看 CDN 速度榜 和 國內怎麼選公共 CDN;面向中國大陸境內的加速需要 ICP 備案,並經由持證服務商。
上線後怎麼驗證?
對照這張檢查表逐項過一遍:
| 檢查項 | 方法 | 預期 |
|---|---|---|
| 不再請求 Google 域名 | grep -rn "fonts.googleapis|fonts.gstatic" dist/;DevTools 篩選 fonts.g | 沒有結果 |
| 字型是 woff2 且同源 | Network 篩選 Font,看 Type 和 Domain | font/woff2,域名是自己的 |
| preload 被複用 | Console 無 “preloaded but not used” 警告;同一檔案只下載一次 | 只有一條請求 |
| 只下載需要的分片 | 開啟一篇中文文章,數 Font 請求 | 只有少數分片,不是整套 |
| 快取頭正確 | curl -I https://你的域名/assets/xxx.woff2 | 有 max-age=31536000,hash 檔案帶 immutable |
| 佈局偏移可接受 | Lighthouse 或 Performance 面板看 CLS | 字型替換時沒有明顯跳動 |
| 弱網下首屏不白 | DevTools 節流到 Slow 4G,重新整理 | 先出後備字型的文字,不等字型 |
還有一個容易漏掉的地方:第三方元件(評論框、圖表庫、文件主題)可能自己帶著 Google Fonts 的 <link> 或 @import。上線後在生產環境再篩一次 fonts.g,比只查原始碼更可靠。
改完之後對比一次
自託管之後,頁面少了兩個外部域名的連線,也去掉了一個會阻塞渲染的第三方樣式表。可以用 線上測速 對比公共 CDN 域名和自己域名的首次連線耗時,再在 DevTools 裡對比改動前後的首次繪製時間,確認實際省下了多少。網路層面的其他排查項可以參考 開發者網路自檢清單(2026 版)。