日本語Webフォントが重い理由と軽量化の方法。自社サイトで実測した。

先日、弊社サイトの読み込みを計測ツールで追いかけていて、妙なことに気づきました。同じページなのに、iPhoneではWebフォントを約740KB読み込み、Androidでは1本も読んでいなかったのです。

グレープフルーツ編集部です。弊社サイトは本文の書体にNoto Sans JPというWebフォントを使っています。「日本語のWebフォントは重い」とよく言われますが、実際に何本のファイルが何KB落ちてきて、表示のどこに効いているのかを数字で見たことはありませんでした。

先に答えると、重いのは事実です。

ただ「Noto Sans JPは何MB」という話と、訪問者が実際に読み込む量は別物で、弊社サイトでは1ページあたり約750〜940KBでした。そして表示の遅さの正体はフォントのファイルそのものではなく、別の場所にありました。

目次

ホームページの文字が一瞬遅れて出る現象

Webフォントの読み込み前後。左はグレーの仮の行と読み込み中の表示、右は本来の書体で出た本文

スマートフォンでサイトを開いたとき、文字が一瞬だけ別の書体で出て、すぐに本来の書体に切り替わる。見たことがある人は多いのではないでしょうか。

これがWebフォントの読み込みです。

パソコンやスマートフォンに入っている書体(システムフォント)ではなく、サイト側が用意した書体のファイルを初回の訪問時にダウンロードして表示する(2回目からはブラウザのキャッシュが効きます)。この仕組みをWebフォントと呼びます。どの端末で見ても同じ書体になるのが利点で、ダウンロードが終わるまで本来の書体で表示できないのが欠点です。

待っている間の挙動はCSSのfont-displayという指定で決まります。swapなら手元の書体でまず表示して、届いたら差し替える。blockなら短い間、文字を隠して待ちます。

MDNの説明では、swapは「ごく短いブロック期間と、無限のスワップ期間」を持つ指定です。

弊社サイトはswapなので、冒頭の「一瞬別の書体で出る」が起きます。文字が出ないまま待たせるよりはいい、という判断です。気にならない人にはどうでもいい一瞬かもしれません。

CSSで書体を指定する、という部分がピンと来なければ、基礎を書いた記事があります。

日本語のWebフォントが重い理由

英語のサイトなら、アルファベットと数字と記号で100字前後を用意すれば足ります。日本語はひらがな・カタカナ・漢字があり、常用の範囲に絞っても数千字です。

弊社でNoto Sans JPを文字の集合ごとに切り出して容量を測ると、英数字・かな・記号にJIS第1水準相当の漢字を足した3,363字で458KB。第2水準相当まで広げた6,753字で950KB。収録文字をすべて入れると約16,000字で2,084KBでした(400ウェイト、woff2形式)。

文字が増えれば、そのまま重くなる。

さらに太さ(ウェイト)が増えればその分も足されます。見出し用の700と本文用の400を両方使うなら、素直に考えれば2倍。私もそう思っていました。実際はそう単純でないことが、後で分かります。

もう一つ、読み込みが2段階になります。

ブラウザはまずGoogle Fontsから「どの文字がどのファイルに入っているか」を書いたCSSを受け取り、それを読んでからフォントのファイルを取りに行きます。

CSSを全部読み終えるまで、ブラウザはフォントが要るかどうかさえ分からない。その間、ページの描画は止まったままです。ここが地味に効くのですが、話は後に回します。

Google自身も2018年9月に日本語フォントを正式対応したときの公式ブログで、日本語フォントは巨大なのでそのまま配らず、小さなスライスに分けて必要な分だけ配ると説明しています。

重いことは、配る側が一番よく知っているのかもしれません。

Noto Sans JPはどう読み込まれているか

grafru.jpで読み込まれるNoto Sans JPの転送量。記事ページは940KB、トップは約750KB、Androidは0KB

弊社サイトはWordPressで、テーマはSWELLです。SWELLのカスタマイザーには「フォント設定」があり、ここでNoto Sans JPを選ぶとGoogle Fontsから読み込む作りになっています(SWELLマニュアル)。

HTMLを見ると、head内にGoogle FontsへのCSSリンクが1行入っていました。指定は400と700の2ウェイトで、display=swap付き。接続を先回りするpreconnectも、preloadもなし。公開してから手を入れずに使ってきた、そのままの状態です。

その状態でLighthouseを回しました。

Lighthouse 12.8.2のCLI、キャッシュなし、回線はシミュレート、各1回。1回だけの計測なので、スコアは多少ぶれます。iPhoneは実機ではなく、端末の種類(ユーザーエージェント)をiPhoneに切り替えたシミュレーションです。ファイルの本数と容量は回線に左右されないので、そこは安定した数字として読んでください。

ページ端末PerfFCPLCPwoff2フォント転送Google FontsのCSS
トップPC810.7秒2.9秒35本751KB60KB・描画ブロック424ms
ブログ記事PC770.8秒3.4秒45本940KB60KB・描画ブロック444ms
トップiPhone722.0秒6.8秒35本737KB60KB・描画ブロック1,128ms
ブログ記事iPhone692.2秒6.8秒45本940KB60KB・描画ブロック1,309ms

ブログ記事のほうは約7,000字の長文ページです。フォントだけで1ページ約750〜940KB。ページ全体の転送量が1,484〜1,968KBなので、半分近くがフォントということになります。

半分、です。

正直、ここまで多いとは思っていませんでした……。

Google Fontsが返すCSSは、圧縮した転送量で60KB。中身は@font-faceという書体の定義が2ウェイト分、ずらりと並んでいます。

Lighthouseの判定条件では、rel="stylesheet"で読む外部CSSのうちmediaで対象外にしていないものは「描画をブロックする資源」です。

このCSSもそれに当たり、ブロックの長さはPCで424〜444ミリ秒、iPhoneで1,128〜1,309ミリ秒でした。

一方で、フォントのファイル35本はCSSが届いた後にまとめて並列で走ります。PCのトップでは684ミリ秒に始まり、956ミリ秒に全部終わっていました。約270ミリ秒幅。本数の多さほどには時間を食っていません。

Androidだけフォントを読んでいなかった

Lighthouseのモバイル計測は、既定ではAndroidのChromeとして動きます。その結果でフォント関連のリクエストが0本でした。

計測ミスかと思いました。

そこで端末の種類(ユーザーエージェント)を変えてHTMLを取り直しました。

端末Google Fontsの読み込み
Windows Chrome、Mac Safari、iPad、iPhone(Safari・Chrome)あり
Android Chrome(スマホ・タブレット)、Android Firefoxなし

HTMLの差分はGoogle FontsへのCSSリンク1行だけ。弊社で端末の種類を切り替えて取得したところ、Android向けのHTMLにはGoogle Fontsの読み込みタグそのものが入っていませんでした。

手がかりはSWELLのマニュアルにありました。

フォント設定の説明に「Android 6.0から標準フォントがNoto Sansになったとのことなので」とあり、Androidでは端末の書体(sans-serif)に任せる、と続きます。

Androidにはもう似た書体が入っているから配らない、という考え方です。ただし「Androidには読み込みタグを出さない」とまで明記した文は見つけていないので、ここは弊社で確かめた挙動として書いています。

つまり弊社サイトのWebフォント約750〜940KBは、iPhone・iPad・PCで見ている人だけが払っているコストです。Androidで見ている人はずっと端末の書体で読んでいて、表示が崩れていると言われたことは今のところありません。

気づかれていないだけかもしれません。

私が一番ひやりとしたのはここで、Lighthouseのモバイル計測だけを見ていたら、Webフォントの重さは一生見えなかったはずです。

同じサイトでも、端末によってWebフォントを読んでいたり読んでいなかったりする。計測するなら、iPhoneとAndroidの両方で見ないと片方しか見えません。

Google Fontsは使う文字の分だけ配っている

Google Fontsが返すCSSの一部。Noto Sans JPの@font-faceがunicode-range付きで並んでいる

CSSの中の書体定義を数えると、400と700でそれぞれ120を超える数がありました。1ウェイトを120を超える小さなファイル(スライス)に分割していて、それぞれに「このファイルはこの文字コードの範囲」というunicode-rangeが付いています。ブラウザはページに出ている文字を含むスライスだけを取りに行きます。

だからトップは35本、長文の記事は45本と、ページに出てくる文字の種類で本数が変わるわけです。

Googleはこの切り方を配信定義ファイルとして公開しています(GitHubのjapanese_default.txt)。使われる頻度の高い文字から小さなスライスに切り、頻度の低い文字は文字コード順に等分する、という作りです。

1ウェイトのスライスをすべて足すと約5.2MBになりますが、実際に落ちるのはその1〜2割。Googleが公式ブログで「ユーザーはページに必要なスライスだけをダウンロードし、通常はフォントの大半を避けられる」と書いているとおりの挙動でした。

ここで意外だったことが2つあります。

1つ目。400と700の2ウェイトを頼んでいるのに、CSSの中で両方が同じURLを指していました。配られているのは可変フォント(1つのファイルで太さを連続的に変えられる形式)のスライスで、実ファイルは1ウェイト分しかありません。700を足しても、ダウンロードの本数は増えません。

その代わり、1本ずつが重くなります。

400だけを頼むと静的フォントのスライス(平均23.1KB)が配られ、400と700を頼むと可変フォントのスライス(平均43.1KB)に切り替わる。1本あたり約1.9倍です。「ウェイトを1つ減らせば半分」ではなく「1本ずつが倍近く重くなる」が正確なところでした。確認時点の挙動なので、Googleが配り方を変えれば変わります。

2つ目。古いcss?family=形式の読み込みを新しいcss2?family=に書き換えると速くなる、という話を見かけます。両方を取得して比べたら、返ってくるCSSは228,130バイトで完全に一致していました。書き換えても何も変わりません。書き換えて速くなった気がしたなら、キャッシュが効いただけではないでしょうか。

css2の公式ドキュメントでは、可変フォントの太さを範囲で指定できるなど書き方の自由度は上がっています。ただ、同じ400と700を頼む限り、中身は同じでした。

やらなくていい作業のひとつです。

自前サブセットでも容量は同じだった

サブセットの範囲ごとのwoff2容量。収録文字すべてで2,084KB、JIS第1水準で458KB、記事1本分で90KB、見出しだけで51KB

Google Fontsに頼らず、必要な文字だけに絞ったフォントを自社サーバーに置く。Webフォント軽量化の定番の手法で、サブセット化と呼びます。

弊社でもやってみました。

Googleが配布しているNoto Sans JPの可変フォント(約9MB)から400と700を切り出し、fontToolsでヒンティングを外してwoff2にしました。文字の集合ごとの容量がこれです。

文字の集合文字数400700
収録文字すべて約16,0002,084KB2,131KB
英数字・かな・記号+JIS第1・第2水準相当6,753950KB971KB
英数字・かな・記号+JIS第1水準相当3,363458KB469KB
記事ページに出る文字だけ61590KB93KB
見出しに出る文字だけ(700のみ)307–51KB

JISの水準はShift_JISのコード帯で近似した範囲です。

第1水準までに絞った400と700を足すと927KB。

Google Fontsが記事ページで実際に配っていた940KBと、ほぼ同じでした。

つまり「全訪問者に同じサブセットのファイルを配る」方式は、少なくともこの記事ページ1回分の比較では、Googleのスライス配信と容量が変わりませんでした。Googleはすでに、ページごとに必要な分だけ配るという、もっと細かいサブセットをやってくれているからです。やる前は半分くらいになると思っていたので、これは拍子抜けでした……。

ただし前提は揃っていません。

自前のファイルは一度落とせば次のページからは読み込みが無く、Googleの方式はページごとに足りないスライスを追加で取りに行きます。サイト内を何ページも読む人ほど、自前のほうが有利になります。

効くのは、ページに出る文字だけに絞る方式(1ウェイト90KB)か、本文は端末の書体にして見出しだけWebフォントにする方式(51KB)です。ただしページ単位のサブセットは、公開するたびに文字を集めてフォントを作り直す工程が要ります。静的に生成するサイトなら組み込めますが、WordPressのように記事を書けばすぐページが生える仕組みには向きません。

字間の落とし穴

サブセットで削れるのは容量だけではなく機能も削れます。fontToolsの公式ドキュメントによると、既定で残るのはカーニングや合字など一部の機能で、日本語の字間を詰めるpaltは既定に含まれていません。

弊社の計測でも、レイアウト機能をすべて残す指定にすると、記事ページ分のサブセットが90KBから121KBに増えました。サブセット後に見出しの字間が間延びして見えたら、ここを疑ってください。

ライセンス

Noto Sans JPはSIL Open Font License 1.1で配布されています。改変しての再配布は許されています。主な条件は単体で売らないこと、改変版に予約済みのフォント名(Reserved Font Name)を使わないこと、配るときはライセンス文を付けること、派生物も同じライセンスにすること。

サブセット化して自社サイトで配るのは範囲内です。ファミリー名は別の名前にしておくのが無難でしょう。

LCPを遅らせているのはフォントではない

Googleがページの体験を測る指標にしているCore Web Vitalsのうち、LCPは「ページの主役になる一番大きな要素が表示されるまでの時間」で、2.5秒以下が良好です。

弊社サイトはPCで2.9〜3.4秒、iPhoneのシミュレーションで6.8秒でした。

良好ではありません。

では、何が遅いのか。

LighthouseはLCPになった要素と、その時間の内訳を出してくれます。表はサーバーの応答を待つ時間(TTFB)を除いた3つです。

ページ・端末LCP要素読み込み待ち読み込み描画待ち
トップ・PCロゴ画像585ms494ms1,522ms
記事・PCロゴ画像759ms544ms1,812ms
トップ・iPhoneロゴ画像1,370ms1,139ms3,681ms
記事・iPhoneロゴ画像1,350ms1,192ms3,668ms

LCPの要素はテキストではなくヘッダーのロゴ画像でした。

そして内訳の半分強は「描画待ち」。画像は届いているのに、描くのが遅れている時間です。ここに効いているのは描画をブロックするCSSとJavaScriptで、Google FontsのCSSはその中で最大のものでした。

フォントのファイルそのものはLCPをブロックしていません。

web.devの説明でも、フォント待ちでブロックされている間のテキストはLCPの候補にならないと書かれています。swapで本文は手元の書体で先に描かれるので、フォント本体の到着をLCPが待つこともない。弊社サイトで表示を遅らせているものの筆頭は「フォント本体の750KB」ではなく「フォントの定義を書いた60KBのCSSを待っている時間」でした。

重いのはフォントで、遅いのはCSS。

測る前は、この2つを同じものだと思い込んでいました。同じ思い込みをしている人は、案外多いのではないでしょうか。

CLSはどの計測でも0でした。書体が切り替わってもレイアウトが動いていない、ということです。

ちなみにGoogleは検索でのCore Web Vitalsの扱いを、Search Centralのページで「Googleのコアランキングシステムがランキングを決定する際に考慮する要素」の一つだと説明しています。

それだけで順位が決まる、とは書いていません。

試算で言える上限はここまで

弊社サイトで施策を当てて前後を比べる再計測は、まだしていません。書けるのは「現状の数字から見た上限」までです。Google Fontsを使い続ける前提だと、打てる手はこの4つに絞られます。

打ち手期待できること根拠になる数字
preconnectを2行足すCSSが届くのが早まるPCでCSSは231msに始まり564msに終わる。この333msの一部が接続の確立
Google FontsのCSSを非同期にする描画ブロックを0に近づける描画ブロック424〜1,309msが上限
700をやめて400だけにする1本あたりが可変→静的で約半分試算で750KB→約400KB、940KB→約500KB
iPhone・PCでもAndroidと同じく読まないNoto Sans JPの転送が0になる750〜940KBをそのまま削れる

preconnectはfonts.googleapis.comとfonts.gstatic.comへの接続をHTMLの先頭で先に始めておく指示です。Googleが運営するweb.devがGoogle Fonts向けに載せている2行がそのまま使えます。

フォント本体はCORS接続で送られるので、fonts.gstatic.com側だけcrossoriginを付けます。この2行の出典はGoogleが運営するweb.devです。

CSSの非同期化は描画ブロックをなくす代わりに、書体の切り替わりが今より目立つかもしれません。すでにswapで切り替わっているので、許容済みの範囲だと私は思っています。

400だけにする案は見出しの太字をブラウザの合成ボールドにするか、別の手段で出すことになります。見た目の判断が入るので、数字だけでは決められません。

4つ目は乱暴に見えますが、Androidの訪問者には今すでに起きていることです。iPhoneならHiragino Sansで表示される。それで困るかどうかは見比べてから決めればいいのではないでしょうか。

やらなくていいこと

cssからcss2への書き換えは、前の章のとおり中身が同じなので効果がありません。

もう一つ、Google FontsのCSSやフォントファイルをpreloadで先読みする、という手法を見かけますが、これも勧めません。Google Fontsの技術資料にあるとおり、CSSは端末ごとに生成されます。

フォントファイルのURLはバージョン(今はv57)で変わり、どのスライスが要るかはページの文字次第。先読みする対象を固定できない相手に、preloadは向きません。

しかもweb.devによれば、preloadはunicode-rangeを無視します。スライス配信の相手に張ると、指定したスライスは要不要にかかわらず落ちることになります。

フォントのpreloadはファイルのURLが固定できるとき(自前ホストなど)の手法で、web.dev自身も「他の資源を妨げるので慎重に」としています。

サブセット化に進むなら、欠字の運用まで決めてから。固定の文言が中心のサイトなら効きますが、入力した文字がそのまま出るサイトでは、入っていない字が出た瞬間に端末の書体に戻ります。

やったほうがいいこと

やったほうがいいこと3つの一覧。preconnectを2行足す、Google FontsのCSSを非同期にする、見出しだけWebフォントにする。左にノートパソコンで作業するキャラクターのイラスト(モノクロ+紫の差し色)

弊社サイトで今回見つかった直しどころは、HTMLの先頭に2行足すことと、CSSの読み方を変えることでした。どちらもテーマや子テーマに手を入れる作業です。

Webフォントの軽量化は、設定画面で済む部分と、テーマに手を入れる部分の線引きがはっきりしています。手を入れるなら、この3つです。

やったほうがいいこと
  1. Google Fontsにpreconnectを2行足す
  2. 描画を止めているGoogle FontsのCSSを非同期にする
  3. 本文は端末の書体、見出しだけWebフォントにできるか試す

1はHTMLの先頭に2行足すだけで、数分で済みます。2と3は見た目が変わるので、プレビューで見比べる時間を含めて見ておいてください。3は本文でしか使わない字の分のフォントが要らなくなるので、効果は大きいはずです。

SWELLで游ゴシックに切り替えて見比べる

SWELLを使っているなら、カスタマイザーのフォント設定でNoto Sans JPから游ゴシック(標準)に切り替えて、プレビューで見比べてみてください。マニュアルの階層は「外観 > カスタマイズ > サイトの全体設定 > 基本デザイン > フォント設定」です。

マニュアルは2021年のものなので、画面の文言は版で少し違うかもしれません。

違いが気にならなければ、それが一番軽い状態です。戻すのも1クリック!

ホームページの保守はWordPressの更新と同じで、作った後に誰が面倒を見るかで差がつきます。

表示速度は見た目の改善と切り離せないので、デザインの古さが気になっているなら、こちらも一緒にどうぞ。

「自社サイトは何KB読んでいるのか」「見出しだけWebフォントにしたらどう見えるのか」くらいの相談はお問い合わせから受けています。ただ、測ってみたら今のままで問題ない、という答えになることも多いはずで、そのときはそう言います。

正直に言うと、測る前は「フォントを軽くすれば速くなる」と思っていました。

測った後の感想は少し違っていて、Androidの訪問者はもうフォントを読んでいないし、遅さの一番大きな原因はCSSの待ち時間でした。弊社サイトもまずpreconnectの2行とCSSの扱いから直して、もう一度測るところからです。

参考・出典

Written by

グレープフルーツ編集部です。
ウェブ制作やマーケティングに関する最新情報、事例紹介、役立つノウハウをお届けしています。

目次