コンテンツにスキップ

ADR-0075: 本文をシステムフォントで描き、Web フォントを記事の題と見出しの明朝と等幅だけにする

Status

承認

Context

和文の書体の読み込みを減らすために、本文の書体と、Web フォントの配信の方法を決め直す必要がある。

  • サイトの基盤の要求は、Lighthouse の Performance が 90 以上、LCP が 2.5 秒以内である。条件は Moto G Power、Slow 4G 相当である。
  • トップを 2 回目以降に訪問したときは、Performance が 75、LCP が 5.71 秒で、要求に届かない(simulate、5 回の中央値)。
    • LCP の要素は、庭の和紙に置いたプロフィールの段落である。この段落は、本文の書体の Noto Sans JP で描かれる。
    • トップは、Noto Sans JP の分割を 28 ファイル、352 KiB 取得する。
    • レンダリングを妨げる CSS は gzip で 179 KiB あり、ほぼ全部が 632 件の @font-face である。Noto Sans JP の 400 と 700 が、そのうち 248 件を占める。
  • 運営者は、和の感じを残すと決めた。書体は変えてよいとした。
  • DADS の書体は Noto Sans JP と Noto Sans Mono で、選んだ理由は可読性と視認性の高いサンセリフであることである。システムの書体を使うことは制限していない。
  • 本文には、PostgreSQL、EXPLAIN、Buffers のような欧文の技術用語が、ほぼ全ての段落に出てくる。欧文の字の大きさと線の太さが和文とずれると、技術用語だけが小さく薄く見える。
  • Fontsource の STIX Two Text は、roman と italic の woff2 を合わせて 263 KiB あり、latin の分割だけで 58 KiB ある。
  • Zen Old Mincho は、仮名の重心が現代の明朝より低いオールドスタイル明朝である。起筆と終筆に筆意は残るが、活字なので筆文字に当たらない。
  • 和文の可変の書体は、1 つの分割がウェイトの軸を持つので重い。Noto Serif JP の可変は、1 つの分割が平均 56 KB だった。Zen Old Mincho の静的なウェイトは、1 つの分割が平均 20 KB だった。
  • プレイグラウンドの Node の実行環境のパスと、それを埋め込む記事の URL は、COEP: require-corp を返す。COEP の下では、CORP を返さないクロスオリジンのリソースがブロックされる。
  • 書体の CSS(packages/ui/src/tokens/fonts.css)は、apps/web、apps/playground、packages/ui の Storybook が読む。
  • apps/web のビルドは、記事の題と見出しに使う Zen Old Mincho の分割を、使う字だけに作り直す(apps/web/src/lib/subset-fonts.ts)。apps/playground と Storybook のビルドは、作り直さない。
  • Fontsource の Noto Sans JP は、unicode-range の分割と、和文の字をまとめたウェイトごとの 1 ファイル(1.1〜1.2 MiB)を持つ。サイトで使う字だけのファイルは持たない。

Decision

本文の和文と欧文を、システムフォントで描く。
Web フォントは、記事の題と見出しの明朝と、等幅だけにする。
本文の Web フォントの取得をなくすと、トップの Performance が simulate の中央値で 90 に届いたためである。

役割 書体 ウェイト 配信の方法
本文 システムフォント。"Hiragino Sans", "Hiragino Kaku Gothic ProN", "Yu Gothic", Meiryo, "Noto Sans CJK JP", sans-serif 400 / 700 読み込まない
記事の題と見出し Zen Old Mincho。代替は本文の font-family の書体 600 Fontsource の分割を self-host し、ビルドで使う字だけに作り直す
等幅 Noto Sans Mono 400 / 700 Fontsource の分割を self-host する
等幅の中の和文 BIZ UDGothic 400 / 700 Fontsource の分割を self-host する
  • 本文の font-family の値は、Verification の計測に使った値で始める。Windows と Android の端末で、本文がどの書体で描かれるかと記事のページの見た目を確認し、確認の結果で、font-family に並べる書体と順を直す。どの書体も無い端末は、ブラウザの sans-serif で描く。
  • 本文の font-family の値は、プリミティブトークンの --fd-primitive-font-family-sans の 1 か所に置く。テーマ変数の --font-sans はこの値を参照し、--font-heading は代替の書体としてこの値を参照する。
  • 本文と見出しに、欧文だけに使う Web フォントを足さない。
    • 欧文の Web フォントを足すと、本文で Web フォントを取得することになるためである。
    • 本文の欧文は、システムフォントの和文の書体が持つ Latin で描く。和文の書体が持つ Latin は、大きさと太さが和文とそろう。
    • 記事の題と見出しの欧文は Zen Old Mincho が持つ Latin で、等幅の欧文は Noto Sans Mono で描く。
  • 和の感じは、記事の題と見出しの明朝で残す。主の色で和の感じを残す判断は ADR-0024 に、挿絵で残す判断は ADR-0026 にある。
  • 和文の Web フォントに、可変の書体を使わない。和文の Web フォントは書体ごとに 1 つか 2 つのウェイトしか使わないので、ウェイトの軸の分だけ転送量を増やす理由がない。
  • Web フォントは Fontsource で self-host し、外部の CDN から読まない。同一オリジンから配信すれば、COEP の下でもサードパーティのヘッダの方針に左右されない。
  • どの Web フォントも font-display: swap にし、和文の分割は preload しない。
  • size-adjust は使わない。本文は Web フォントを読み込まないので、本文では差し替えのずれが起きないためである。

Verification

トップを 2 回目以降に訪問したときの値を、pnpm run measure:top で測った(Metal、mobile、simulate、各案 5 回)。
対象は develop の 5825f0e に、案ごとの実験の変更を当てたものである。
システムフォントの案は、Decision の表の本文の font-family で測った。
本文は macOS の Hiragino Sans で描かれた。

案 Performance(中央値と範囲) LCP(中央値) FCP(中央値) フォントのリクエスト フォントの転送量 CSS の転送量
システムフォント 92(88〜92) 2.86 s 1.50 s 6 34 KiB 112 KiB
サブセット(1 ファイル) 89(85〜89) 3.30 s 1.50 s 8 124 KiB 112 KiB
サブセット(分割のまま) 82(80〜82) 4.21 s 1.80 s 34 143 KiB 179 KiB
今の構成 75(70〜92) 5.71 s 1.80 s 34 386 KiB 179 KiB
  • サブセットの 2 つの案は、サイトで使う 291 字だけのファイルで測った。
  • サブセット(1 ファイル)の案は、google/fonts の Noto Sans JP の可変の書体を 400 と 700 に固定し、pyftsubset のデフォルトの layout の feature で作った。1 ファイルは 44〜45 KiB だった。layout の feature を全て残すと、1 ファイルが 68〜70 KiB になり、Performance は 87 だった。
  • 今の構成で Performance が 92 になった 1 回は、庭の WebGL の最初のフレームがページの最初の描画に入り、LCP が FCP と同じ 2.71 秒になった回である。
  • devtools のスロットリングで 6 回ずつ測った。
    • システムフォントの案は、Performance が 80〜82、LCP が 3.56〜3.61 秒、TBT が 240〜281 ms だった。
    • 今の構成は、4 回が Performance 79〜84、LCP 3.42〜3.86 秒、TBT 0 ms だった。2 回は load が 7.3〜7.4 秒まで延び、Performance 53 と 58、LCP 7.35〜7.46 秒だった。
    • システムフォントの案に、load が延びる回は無かった。
  • どの案も、LCP の 2.5 秒には届かなかった。
  • 合否に使う測り方(GPU とスロットリング)は、運営者が決める。
  • 最初の訪問、記事のページ、Windows と Android の端末は測っていない。

Considered Options

選択肢 強み 弱み
採用: 本文をシステムフォントにする 測った 4 つの案で唯一、Performance が simulate の中央値で 90 に届いた(92)。本文の字が増えても、取得する量が変わらない。変えるのは、プリミティブトークンと fonts.css、生成したトークン、依存 1 つだけで、apps/web、apps/playground、Storybook に同じ変更が入る 本文の字形が端末ごとに違う。DADS の Noto Sans JP から外れる。字の幅と行の折り返しが端末ごとに変わる
Noto Sans JP を、サイトで使う字だけのウェイトごとの 1 ファイルにする(サブセット(1 ファイル)の案) 字形がどの端末でも同じになる。Performance が 75 から 89 に上がった Performance は 89 で、90 に届かない。記事が増えて字が増えると、ファイルが大きくなる。apps/playground と Storybook にも、作り直す処理が要る。訪問者が入力した、ファイルに無い字は別の書体で描かれる
Noto Sans JP の分割を残し、各分割を使う字だけにする(サブセット(分割のまま)の案。見出しの明朝と同じ処理) 見出しの明朝の処理をそのまま使える Performance は 82 で、90 に届かない。リクエストの数と、レンダリングを妨げる CSS の @font-face が減らない
今の構成(Noto Sans JP の分割をそのまま配信する) DADS と同じ書体で、字形がどの端末でも同じになる Performance が 75、LCP が 5.71 秒で、要求に届かない
BIZ UD の一族でそろえる(本文 BIZ UDPGothic、見出し BIZ UDPMincho) 本文、見出し、等幅の字形がそろう 和の感じが弱い。本文の Web フォントの取得が残り、今の構成と同じ理由で要求に届かない見込みである(測っていない)
欧文に STIX Two Text を足す x-height と線の太さが和文に近く、技術用語が和文と同じ大きさと太さで読める 本文で Web フォントを取得することになる。latin の分割だけで 58 KiB ある
本文も明朝(Zen Old Mincho) 和の感じが最も強い 本文の字が多く、読み込む量が最も大きい。DADS がサンセリフを選んだ理由とも合わない

Consequences

Positive

  • 本文の Web フォントの取得がなくなり、レンダリングを妨げる CSS が gzip で 179 KiB から 112 KiB に減る。トップの FCP は 1.80 秒から 1.50 秒になった。
  • 代替の書体から差し替わるときのずれが、本文で起きない。
  • 記事のページも、本文の Web フォントを取得しなくなる。記事のページの値は測っていない。

Negative

  • 本文の見た目が、端末に入っている書体ごとに変わる。Hiragino Sans、Yu Gothic、Meiryo、Noto Sans CJK JP のどれで描くかで、字の幅、行の折り返し、太さが違う。
  • DADS の書体から、本文も外れる。DADS のコンポーネントの見た目は、システムフォントで描いた状態で確認する。
  • 記事の題と見出しは、Zen Old Mincho が読み込まれるまで本文の font-family の書体で描かれ、読み込まれた後に明朝へ差し替わる。size-adjust を使わないので、差し替わるときに字の幅と行の折り返しがずれる。
  • 欧文の字形と大きさが、端末に入っている和文の書体ごとに変わる。
  • LCP の 2.5 秒には、この決定だけでは届かない。2.5 秒を超える分は、庭のバンドルの取得によると推測する。
  • レンダリングを妨げる CSS には、見出しの明朝と等幅の @font-face が 384 件残る。
  • 見た目の回帰テストでは、スクリーンショットの本文が、テストを実行する環境に入っている書体で描かれる。