Webフォント最適化 2026年版 実践ガイド:サブセット化・Variable Fonts・font-displayでLCPとCLSを改善する

Webフォント最適化の実践ガイド。WOFF2変換、サブセット化、Variable Fonts、font-display、preload、size-adjustを使い、日本語フォントのLCP・CLSを大幅に改善する2026年版テクニックをコード例付きで解説します。

Webフォント最適化 2026年 実践ガイド

更新日: 2026年8月13日

Webフォント最適化とは、フォントファイルのサイズ削減、読み込みタイミングの制御、フォールバック指定を組み合わせてLCP・CLS・レンダリング時間を短縮する一連の技術です。2026年時点で最も効果が大きい施策は、WOFF2形式への変換、unicode-range によるサブセット化、Variable Fontsの採用、font-display: swapsize-adjust の併用、そして重要フォントの <link rel="preload"> 指定です。

特に日本語フォントは1書体あたり2〜15MBに達するので、サブセット化の効果はLatinフォント比で桁違いに大きくなります。前職でECサイトのリニューアルを手伝ったとき、Noto Sans JPをそのまま読み込んでLCPが4.8秒になっていた案件を、サブセット化とpreloadの合わせ技だけで1.9秒まで縮められました。フォント周りは、正直やるかやらないかで結果が変わりすぎるんです。

  • WOFF2は非圧縮TTFと比較して平均30%以上サイズを削減でき、2026年時点で全モダンブラウザが対応済みです。
  • font-display: swap は FOUT(文字の点滅)を許容してレンダリングを最速化し、optional はCLSを厳格に防ぎたい場合に有効です。
  • Noto Sans JP のような日本語フォントを subset-fontglyphhanger で使用文字だけに絞ると、5MBが100KB以下になるケースも珍しくありません。
  • Variable Fontsは複数ウェイト(Regular/Bold/Light)を1ファイルに統合でき、通常3〜4リクエストを1リクエストに削減できます。
  • size-adjustascent-overridedescent-override を使ったフォールバック調整で、フォントスワップ時のCLSを実質ゼロにできます。
  • Google Fontsのセルフホスティングは、サードパーティ接続を1つ減らしTTFB短縮とプライバシー要件の両方に効きます。

Webフォントがパフォーマンスに与える影響

Webフォントは、Core Web Vitalsの3指標すべてに影響します。まずLCPですが、ヒーロー領域の見出しがWebフォントで描画される場合、フォントのダウンロードとパース完了までテキストが表示されない(FOIT)か、フォールバックで一瞬表示されてから切り替わる(FOUT)ため、LCP計測が遅延しがちです。

次にCLSについて。フォールバックフォントとWebフォントで文字の幅・高さ・行送りが異なると、フォントスワップの瞬間に大きなレイアウトシフトが発生します。CLSの原因調査については「CLS完全攻略ガイド」でも詳しく扱っているので、あわせて読むと理解が深まります。

最後にINP・レンダリング時間への影響として、フォントファイルの解析はメインスレッドで行われるため、5MBのNoto Sans JPをそのまま読み込むと、低スペック端末では200ms以上のブロッキングが発生することもあります。ブラウザは font-face ルールを解析後、実際にそのフォントを使うテキストがDOMに現れた時点で初めてダウンロードを開始するため、CSSファイルが遅延すればフォント読み込みも連鎖的に遅延します。この「発火の遅さ」を打ち消すのが、後述する preloadfont-display のチューニングです。

WOFF2形式への変換と圧縮の基本

2026年時点で、Webフォントの配信フォーマットは WOFF2一択と言い切っていいです。WOFF2はBrotli相当の圧縮アルゴリズムを内蔵しており、非圧縮TTF比で平均30%、旧WOFF比でも約20%小さくなります。IE11のシェアが実質ゼロになった今、WOFFやTTFフォールバックを併記する必要はほぼありません。Can I use のWOFF2対応状況を確認すると、グローバルで99%以上のブラウザが対応済みです。

既存のOTF/TTFファイルをWOFF2へ変換する最短ルートは、Googleの woff2_compress コマンドラインツールか、Pythonの fonttools です。以下は fonttools を使った変換例です。

# 変換ツールをインストール
pip install fonttools brotli zopfli

# OTF/TTFをWOFF2に変換
pyftsubset NotoSansJP-Regular.otf \
  --output-file=NotoSansJP-Regular.woff2 \
  --flavor=woff2 \
  --with-zopfli

# 既存WOFF2の圧縮率をさらに上げたい場合
python -m fontTools.ttLib.woff2 compress \
  --brotli-quality=11 \
  input.ttf

CDN側で Cache-Control: public, max-age=31536000, immutable を返すことも忘れないでください。フォントは内容が変わることが少なく、ファイル名にハッシュを含めれば恒久的にキャッシュ可能です。CDN戦略とTTFB削減の基本を組み合わせると、フォント配信のRTTを一桁ミリ秒台に抑えられます。

font-display の使い分け:swap・optional・fallback

font-display ディスクリプタは、フォント読み込み中の描画挙動を制御する最重要プロパティです。値は autoblockswapfallbackoptional の5種類あり、それぞれ「ブロック期間」と「スワップ期間」の長さが異なります。MDN の font-display ドキュメントに詳細な仕様が定義されています。

ブロック期間スワップ期間用途
autoブラウザ依存ブラウザ依存非推奨(挙動が不定)
block約3秒無限ロゴなど絶対に代替表示したくないもの
swap0ms無限本文・見出しの標準選択
fallback約100ms約3秒短時間だけ待って諦める中庸案
optional約100ms0msCLSを厳格に防ぎたい・低速回線を切り捨ててよい場合

実装例です。本文には swap、装飾フォントには optional を使うといった使い分けが有効です。

@font-face {
  font-family: "Noto Sans JP";
  src: url("/fonts/NotoSansJP-Regular.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap; /* 見出し・本文 */
}

@font-face {
  font-family: "Zen Kaku Gothic";
  src: url("/fonts/ZenKakuGothic-Bold.woff2") format("woff2");
  font-weight: 700;
  font-display: optional; /* 装飾用:初回訪問では表示されない可能性を許容 */
}

サブセット化の方法:unicode-rangeと日本語フォント

サブセット化とは、フォントファイルから実際に使う文字のグリフだけを抽出して、ファイルサイズを劇的に削減する技術です。英語フォントで50〜70%、日本語フォントでは95%以上の削減が可能です。Noto Sans JP Regularは全字収録で約5.4MBありますが、常用漢字+ひらがな+カタカナ+ASCIIだけに絞ると300KB以下、さらにサイト内で実際に使う文字だけに絞れば100KB未満まで縮小できます。

手動サブセット化には pyftsubset(fonttools付属)が最も柔軟です。

# JIS第一水準+ひらがな+カタカナだけ抽出
pyftsubset NotoSansJP-Regular.otf \
  --output-file=NotoSansJP-Regular-subset.woff2 \
  --flavor=woff2 \
  --unicodes="U+0020-007E,U+3000-303F,U+3040-309F,U+30A0-30FF,U+4E00-9FFF" \
  --layout-features='*' \
  --no-hinting

# サイト内で実際に使われている文字だけを自動抽出
npx glyphhanger https://example.com \
  --subset=NotoSansJP-Regular.otf \
  --formats=woff2

もう一つの手法が unicode-range ディスクリプタです。文字範囲ごとに複数の @font-face を宣言することで、ブラウザは実際にその範囲の文字がページ内に出現したときだけ該当ファイルをダウンロードします。Google FontsのAPIレスポンスはまさにこの仕組みで、日本語フォントを100個以上のシャードに分割配信しています。

/* ASCII範囲は軽量な英字専用ファイル */
@font-face {
  font-family: "MyFont";
  src: url("/fonts/myfont-latin.woff2") format("woff2");
  unicode-range: U+0020-007E;
  font-display: swap;
}

/* ひらがな・カタカナは別ファイル */
@font-face {
  font-family: "MyFont";
  src: url("/fonts/myfont-kana.woff2") format("woff2");
  unicode-range: U+3040-30FF;
  font-display: swap;
}

/* 常用漢字はさらに別ファイル */
@font-face {
  font-family: "MyFont";
  src: url("/fonts/myfont-kanji-common.woff2") format("woff2");
  unicode-range: U+4E00-9FAF;
  font-display: swap;
}

Variable Fontsで複数ウェイトを1ファイルに統合

Variable Fonts(可変フォント)は、Regular・Medium・Bold・Blackなど複数のウェイトやスタイルを1つのファイルに格納できるOpenTypeの拡張仕様です。従来は書体ごとに4〜9ファイルをダウンロードしていましたが、Variable Fontsなら1リクエストで完結します。Noto Sans JP Variable の場合、7ウェイトを個別にDLすると合計30MB超になるところが、Variable版なら8〜10MB程度、サブセット化と組み合わせれば数百KBに収まります。

@font-face {
  font-family: "Inter Variable";
  src: url("/fonts/Inter.var.woff2") format("woff2-variations"),
       url("/fonts/Inter.var.woff2") format("woff2");
  font-weight: 100 900; /* ウェイト範囲を指定 */
  font-stretch: 75% 125%;
  font-display: swap;
}

/* 使う側はfont-weightに任意の値を指定できる */
h1 { font-weight: 750; } /* SemiBoldとBoldの中間 */
p  { font-weight: 400; }
.strong { font-weight: 620; }

ただし注意点として、Variable Fontsのファイル自体は静的フォント1書体よりは大きくなります。「Regularだけしか使わない」ようなシンプルなサイトでは、逆に静的フォントの方が軽量です。3ウェイト以上を使う場合にVariable Fontsのメリットが逆転します。

preload・preconnectによる先読み戦略

ブラウザがWebフォントのダウンロードを開始するのは、CSSがパースされ、かつそのフォントを使う要素がDOMに現れた時点です。ヒーローセクションのフォントほど、この「発火の遅さ」がLCPに直撃します。解決策が <link rel="preload"> によるフォントの先読みです。

<head>
  <!-- 最重要のフォントだけpreload -->
  <link rel="preload"
        href="/fonts/NotoSansJP-Regular-subset.woff2"
        as="font"
        type="font/woff2"
        crossorigin>

  <!-- 外部ドメインを使う場合は先にpreconnect -->
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>

  <!-- 主要CSSファイル -->
  <link rel="stylesheet" href="/main.css">
</head>

preloadする際の落とし穴が3つあります。まず crossorigin 属性は必須です(これ、忘れるとブラウザが同じフォントを2回ダウンロードします。私自身、以前これで丸2日ハマりました)。次に、preloadしすぎるとブラウザの帯域競合が起きて逆にLCPが悪化するので、preloadは1〜2ファイルに絞りましょう。最後に、preloadした後にCSS側で font-display: optional を指定すると、ブラウザはpreloadしたフォントを本来より早くレンダリングに使えるため相性が良好です。画像のpreload戦略については「画像最適化でLCPを劇的改善」も参照してみてください。

size-adjustとascent-overrideでCLSを防ぐ

font-display: swap の最大の欠点は、フォールバックフォントとWebフォントで字形サイズが異なる場合、スワップ時に大きなCLSが発生することです。2022年以降サポートが広がった size-adjustascent-overridedescent-overrideline-gap-override ディスクリプタを使うと、フォールバック側の「見た目のサイズ」をWebフォントに合わせて調整でき、CLSを実質ゼロにできます。web.dev の size-adjust 解説に詳細なアルゴリズムがあります。

/* システムフォントを"Noto Sans JP風"に調整 */
@font-face {
  font-family: "Noto Sans JP Fallback";
  src: local("Hiragino Kaku Gothic ProN"),
       local("Yu Gothic"),
       local("Meiryo");
  size-adjust: 105%;         /* 5%大きく見せる */
  ascent-override: 88%;      /* 上端位置を調整 */
  descent-override: 22%;
  line-gap-override: 0%;
}

body {
  font-family:
    "Noto Sans JP",
    "Noto Sans JP Fallback",
    system-ui,
    sans-serif;
}

調整値の算出は手動では正直難しいので、Malte Ublが公開している Fontpie や、Chrome DevToolsの「Rendering」パネルにあるフォント計測機能を活用してください。数値を1〜2%変えるだけで、CLSスコアが0.15から0.001未満まで落ちるケースも珍しくありません(本当に劇的に変わります)。

Google Fontsのセルフホスティングとメリット

Google Fonts の fonts.googleapis.com から直接読み込む方式は手軽ですが、2026年時点では以下の3つの理由からセルフホスティングが推奨されます。第一に、追加のDNS解決とTLSハンドシェイクが発生するため、TTFBが80〜200ms悪化します。第二に、GDPRおよび日本の改正個人情報保護法の観点で、ユーザーのIPアドレスが第三者に送信されることを避けたい要件が増えています(2022年のドイツ地裁判決以降、Google Fonts利用が違法とされる事例も出ています)。第三に、キャッシュ分離ポリシー(Chrome 86以降)により、以前あった「他サイトでキャッシュ済みだから速い」というメリットが消失しました。

セルフホスティングは google-webfonts-helper を使うと数クリックで完結します。ダウンロードしたWOFF2ファイルを /fonts ディレクトリに配置し、生成されたCSSをコピーするだけです。

/* google-webfonts-helperが生成するCSS例 */
@font-face {
  font-family: 'Noto Sans JP';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/fonts/noto-sans-jp-v52-japanese-regular.woff2') format('woff2');
}

最適化効果の測定方法

フォント最適化の効果を定量的に測るには、Chrome DevToolsの「Network」パネル、Lighthouse、そしてWebPageTestが定番です。Networkパネルで font フィルタを適用すれば、フォントリクエストのタイミング・サイズ・優先度が一覧できます。Lighthouseは以下の観点でスコアします。

  • Ensure text remains visible during webfont loadfont-display指定の有無をチェック
  • Avoid enormous network payloads:フォントファイルが大きすぎないか
  • Preload key requests:クリティカルフォントがpreloadされているか
  • Reduce unused CSS@font-faceが使われていない場合の警告

本番環境ではCore Web VitalsのRUM(Real User Monitoring)を必ず設置してください。web-vitalsライブラリでLCP・CLS・INPを計測し、フォント最適化前後で比較すれば、施策の効果が数字で確認できます。特に低速回線・低スペック端末での改善幅が大きく出ることが多いので、パーセンタイル別の分析が重要です。INP側の追い込みは「INP最適化 実践ガイド」に別途まとめてあります。

よくある質問

Webフォントの読み込みを速くする最短の方法は?

優先順位は「WOFF2化 → サブセット化 → font-display: swap → 最重要フォントのpreload」の順です。特にサブセット化は日本語フォントで95%以上のサイズ削減が可能で、単一施策としての効果が最大です。

font-display: swap と optional の違いは?

swap はダウンロード完了後に必ずWebフォントへ切り替えるため FOUT が発生します。optional は100ms以内に間に合わなければWebフォントを完全に諦めるため、CLSはゼロですが低速回線ではブランドフォントが表示されません。ブランド重視なら swap、CLS重視なら optional を選びます。

日本語フォントのサブセット化はどう行いますか?

pyftsubset(Python fonttools)でUnicode範囲を指定して抽出するか、glyphhanger で実際にサイトで使われる文字だけを自動抽出します。JIS第一水準+かなに絞れば約300KB、実使用文字のみなら100KB未満まで削減できます。

Variable Fontsは常に使うべきですか?

3ウェイト以上を使う場合はVariable Fontsが有利ですが、Regularだけしか使わないサイトでは静的フォントの方が軽量です。ファイルサイズと使用ウェイト数のトレードオフで判断してください。

Google Fontsは遅いのですか?

2020年以前は「他サイトでキャッシュ済み」というメリットがありましたが、Chrome 86以降のキャッシュ分離ポリシーでその優位性は失われました。現在はDNS解決とTLSハンドシェイクの追加コストが純粋な遅延要因となるため、セルフホスティングが推奨されます。

Editorial Team
著者について Editorial Team

Our team of expert writers and editors.