Notes / Web技術

CJK Variable Fontの"CSSが大きい"は罠。unicode-range サブセットの実態

コーポレートサイトのCSSバンドルが446KBに膨れる。「フォントロードがレンダリングをブロックしてFCPやLCPに直撃」というレビュー指摘の妥当性を分解します。@fontsource-variableの unicode-range サブセット仕組みと、定義サイズとペイロードの違い。

Web Font unicode-range CJK performance

コーポレートサイトのCSSバンドルが446KBに膨れたとき、AIレビューに「フォントロードがレンダリングをブロックしてFCPやLCPに直撃」と指摘されました。けれどLighthouseの数値は崩れていません。実際に何が起きているのか、確かめてみます。

CJK(中国語、日本語、韓国語)のフォントは文字数が多く、単純に全部読み込めば数MBになります。最近のWebフォント配信は、unicode-range によるサブセット分割が前提。CSS内の定義サイズと、実際にダウンロードされるフォント本体のサイズは別物です。

CSS 446KBは本当にペイロードか

問題のCSSは @fontsource-variable/noto-serif-jp/wght.css をインポートしていました。ビルド後のCSSをのぞくと、こんな形が連なっています。

@font-face {
  font-family: 'Noto Serif JP Variable';
  font-style: normal;
  font-display: swap;
  font-weight: 200 900;
  src: url(./files/noto-serif-jp-japanese-VF.woff2) format('woff2-variations');
  unicode-range: U+3041-3096, U+3099-309F, ...;
}

@font-face {
  font-family: 'Noto Serif JP Variable';
  ...
  src: url(./files/noto-serif-jp-latin-VF.woff2) ...;
  unicode-range: U+0000-00FF, ...;
}

/* ...複数のサブセットブロックが続く... */

ファイル全体は446KBのCSS。raw 数字だけ見ると「レンダリングをブロックして重い」と思える。けれどこれは font-face の定義で、フォント本体ではありません。

@fontsource-variableの中身

@fontsource-variable/noto-serif-jp (5.x 系) は、Noto Serif JP Variable Font を多数の unicode-range サブセットに分割しています。手元のバージョンを確認すると、番号付きの日本語系サブセット(japanese-0 から japanese-119 程度)が中心で、これに latincyrillicvietnamese 等の補助サブセットが加わる構成です。

CSSの @font-face ブロックは「このunicode範囲が必要になったら、このwoff2を取得しろ」という指示書。多数のサブセット分の指示書なので、定義テキスト自体は数百KBに膨らみます。

ブラウザがサブセットを選んで取得する仕組み

最近のブラウザ(Chromium、WebKit、Firefox)は、ページ内のテキストをスキャンし、実際に使われている文字(unicodeコードポイント)を解析します。その上で、CSSの unicode-range を照らし合わせ、必要なサブセットの woff2 だけをダウンロードします。

日本語のコーポレートサイトでも、ページ内の漢字構成によって取得されるサブセットは変わります。1 サブセットあたり 20〜120KB 程度の幅があり、ページ内の文字種が広いほど複数チャンクを取得することに。実際に何 KB ダウンロードされるかは、DevTools の Network タブでフィルタを font にして実測するのが確実です。

font-display: swapの役割

@fontsource のデフォルトは font-display: swap。Webフォントの取得を待たずにフォールバックフォントで先に表示し、woff2が届いた段階で差し替えます。FOIT(Flash of Invisible Text)は起きず、FOUT(Flash of Unstyled Text)のみ。

CJKではフォールバックフォントとして、OS標準のHiragino、Noto Sans CJKが使われます。Webフォントとの文字メトリクス差で再描画時に微かなレイアウトシフトが起きる可能性があるので、size-adjust の調整は別途検討します。

AIレビューが数値を主因と決めつけたら、実測で返す

AIコードレビューは構造と網羅性が優秀。けれど数値の主因推定で誤読をすることがあります。今回の指摘は「CSS 446KBがレンダリングブロックでFCPやLCP直撃」でした。実態は (a) 446KBはfont-face定義で、実際にダウンロードされる woff2 はその一部、(b) このサイトでは Lighthouse mobile TOP が 93 を維持していた、というもの (計測条件は別途記録)。指摘の方向性は誤りではなくても、優先度判定は P1 ではなく P2 が妥当でした。

数値を見て即「重い」と判定する前に、その数値は何を計測しているのか、実ペイロードはどれか。ここを確認する一手間が要ります。


CJK Variable Fontの「CSSが大きい」は、ほとんどの場合 font-face 定義の話。unicode-range サブセットにより、実 woff2 ダウンロードは定義サイズの一部に限定されます。重いと感じたら、DevToolsのNetworkで実測して切り分けます。

Webパフォーマンス改善のご相談はお問い合わせから、業務領域の詳細はServicesへ。