字型為什麼會拖慢網站

當瀏覽器遇到一個需要特定字型的文字時,如果字型還沒下載完成,它有幾種選擇:

FOIT(Flash of Invisible Text):等字型下載完再顯示,結果用戶先看到空白文字。

FOUT(Flash of Unstyled Text):先用系統字型顯示,字型載入後突然替換,造成視覺跳動。

這兩種情況都會傷害用戶體驗,也影響 Cumulative Layout Shift(CLS)分數。

另外,如果你直接在 CSS 中使用 @import url('https://fonts.googleapis.com/...') 方式引入,會造成額外的 DNS 查詢 + HTTP 請求,可能讓 FCP(First Contentful Paint)變慢 200–500ms。

在 Next.js 中使用 next/font(推薦做法)

Next.js 13 以上版本提供了 next/font 模組,它會在建置時自動:

  1. 從 Google Fonts 下載字型檔案並本地化(放到你的靜態資源中)
  2. 生成最佳化的 @font-face 設定
  3. 消除 Google Fonts CDN 的外部請求(對隱私和效能都有好處)
  4. 自動處理 font-display 策略

基本用法(在 app/layout.tsx 中):

從 next/font/google 引入 Noto_Sans_TC(或其他字型),設定 subsets、weight、display 等參數,然後在 body 的 className 上套用字型的 className 或 variable。

關鍵設定參數說明

weight:指定你需要的字重。不要引入不需要的字重(每個字重都是一個單獨的字型檔案)。

subsets:指定字元子集。使用中文字型一定要設定正確的子集(例如 chinese-traditional 或 chinese-simplified),否則可能下載整個字符集,檔案大小問題很嚴重。

display:控制 FOUT/FOIT 行為。常用值:

  • swap:先顯示備用字型,字型載入後切換(有 FOUT,但文字不會隱藏)
  • optional:讓瀏覽器決定是否載入(對快取後很快,初次可能用系統字型,適合效能極度要求的場景)
  • block:短暫隱藏文字等待字型(有 FOIT,不建議)

variable:生成 CSS 變數(Custom Property),讓你在 Tailwind 的 CSS 變數中引用字型,更靈活。

中文字型的特殊挑戰

中文字型的字符數量龐大(10,000+ 字符),一般全集字型動輒 5–10MB,對任何網站都是無法接受的。

解決方案 1:使用 Google Fonts 的子集化(Subsetting)

Google Fonts 會根據頁面實際使用的字符,動態生成只包含那些字符的字型子集(通過 unicode-range 和按需載入)。

解決方案 2:使用系統字體為備用

在 font-family 設定中,先列本地化自訂字型,再設定 "Noto Sans TC", "微軟正黑體", sans-serif 等備用系統字型。即使自訂字型還在載入,系統字型也能確保文字正常顯示。

解決方案 3:考慮系統字體棧(System Font Stack)

對效能要求非常高的情況,完全使用 system-ui, -apple-system, "Helvetica Neue", sans-serif 這樣的系統字體棧,不引入任何外部字型——零載入時間,自然融入不同作業系統的設計語言。

preconnect 和 preload

如果你必須用傳統的 link 標籤引入 Google Fonts(例如在非 Next.js 的專案),在 HTML head 中加入:

preconnect 到 fonts.googleapis.com 和 fonts.gstatic.com,能讓瀏覽器提前建立到 Google Fonts CDN 的連線,減少延遲。

preload 特定字型檔案(.woff2 格式)能讓最重要的字重在頁面解析初期就開始下載,而不是等到 CSS 被解析後才開始。

用 font-display: swap 避免 FOIT

無論什麼引入方式,確保你的 @font-face 設定中有 font-display: swap(或 optional)。這讓瀏覽器在字型下載期間使用備用字型顯示文字,避免文字完全不可見。

next/font 的 display: 'swap' 參數就是做這件事。