この記事の対象: 日本語Webフォントを使ったサイトで、文字がチカチカ切り替わる現象を今日中に抑えたい制作者
読了時間: 約12分
ページを開いた瞬間、本文が明朝で出てからゴシックに入れ替わる。見出しの幅が変わってレイアウトが跳ねる。あるいは数百ミリ秒のあいだ、文字が一切見えない。どれもWebフォントの読み込み中に起きる表示の揺れで、FOUT(Flash of Unstyled Text)やFOIT(Flash of Invisible Text)と呼ばれます。
英語圏の記事をそのまま試しても、日本語では効かないことが多い。理由は単純で、ファイルサイズが二桁違うからです。この記事では日本語フォントを前提に、font-display の選択、preload の使いどころ、代替フォントとのサイズ差を埋める指定を、そのままコピーして動くコードで示します。
前提と環境

作業を始める前に、この記事が想定している条件を確認してください。
| 項目 | 前提 |
|---|---|
| 対象 | 静的HTML/WordPress(ブロックテーマ・クラシックテーマどちらも)/Shopify(Online Store 2.0) |
| フォントの置き場所 | 自ホスティング(サーバーに .woff2 を置く)を基本とする |
| 必要な権限 | CSSファイルと <head> 内を編集できること |
| 外部ライブラリ | 不要。素のHTML/CSS/JavaScriptだけで完結する |
| ブラウザ | font-display はChrome 60以降・Safari 11.1以降・Firefox 58以降で有効。size-adjust 系はChrome 92以降・Firefox 89以降・Safari 17以降 |
Google FontsやAdobe Fontsのようなホスト型サービスを使っている場合、preload と size-adjust の適用範囲が変わります。その差は後半の「ホスト型サービスを使っている場合」で扱います。
なぜ日本語だと話が変わるのか
欧文フォントの .woff2 は、1ウェイトあたり20〜40KB程度に収まることが珍しくありません。対して日本語フォントは収録文字数が数千〜2万字を超えるため、サブセット化していない状態では1ウェイトで数MBに達します。
日本語フォント
- 収録文字数が数千〜2万字超
- サブセットなしでMB単位
- 読み込み中に本文が読めない時間が長い
- 「見えない」を選ぶと致命的
欧文フォント
- 収録文字数が数百字
- 1ウェイト数十KB
- 読み込みは一瞬で終わることが多い
- 短時間の非表示なら許容できる
この差から、日本語サイトでは「文字を隠して待つ」戦略が成立しにくいと分かります。読めない時間が長くなるほど、離脱にも読了にも効いてしまうためです。
font-display の5つの値を使い分ける

font-display は @font-face の中に書く記述子で、フォントの読み込み中と読み込み後にブラウザが何をするかを決めます。理解のカギは「ブロック期間」と「スワップ期間」という2つの時間です。
- ブロック期間: この間、文字は不可視(フォントは適用されるが描画されない)
- スワップ期間: この間に読み込みが終われば、代替フォントからWebフォントへ差し替える
| 値 | ブロック期間 | スワップ期間 | 実際の見え方 |
|---|---|---|---|
auto |
ブラウザ依存 | ブラウザ依存 | 多くの実装で block 相当。制御できない |
block |
約3秒 | 無制限 | 最初は文字が見えない(FOIT) |
swap |
約100ms | 無制限 | すぐ代替で表示され、後から入れ替わる(FOUT) |
fallback |
約100ms | 約3秒 | 3秒を過ぎたら代替のまま確定する |
optional |
約100ms | なし | 間に合わなければWebフォントを使わない |
ブロック期間・スワップ期間の秒数は仕様上「推奨値」であり、ブラウザによって多少前後します。DevToolsのNetworkパネルでスロットリングをかけながら確認するのが確実です。
日本語サイトでの推奨は fallback または optional
swap は広く勧められている値ですが、日本語では扱いに注意が要ります。スワップ期間が無制限なので、10秒後に読み込みが終わればその時点で文字が入れ替わる。読者が読み始めたあとにレイアウトが動くのが最も体験を損ないます。
判断の軸はこうです。
本文・見出し・アイコンで別々の値を使ってよく、むしろ分けたほうが体験が整います。
書き方
CSSの @font-face に1行足すだけです。
/* assets/css/fonts.css */
/* 本文用:3秒で決着をつけ、それ以降は入れ替えない */
@font-face {
font-family: "NotoSansJP";
src: url("/assets/fonts/NotoSansJP-Regular-subset.woff2") format("woff2");
font-weight: 400;
font-style: normal;
font-display: fallback;
unicode-range: U+3000-30FF, U+4E00-9FFF, U+FF00-FFEF, U+0020-007E;
}
/* 見出し用:文字数が少ないので swap で早く出す */
@font-face {
font-family: "NotoSansJP";
src: url("/assets/fonts/NotoSansJP-Bold-subset.woff2") format("woff2");
font-weight: 700;
font-style: normal;
font-display: swap;
unicode-range: U+3000-30FF, U+4E00-9FFF, U+FF00-FFEF, U+0020-007E;
}
/* アイコンフォント:代替では読めないので待たせる */
@font-face {
font-family: "SiteIcons";
src: url("/assets/fonts/site-icons.woff2") format("woff2");
font-display: block;
}
変更する箇所
font-familyの"NotoSansJP""SiteIcons"— 実際に使うフォント名srcのパス — サーバー上の実際の配置場所unicode-range— 収録範囲。上の例はひらがな・カタカナ・記号(U+3000-30FF)、常用漢字を含むCJK統合漢字(U+4E00-9FFF)、全角英数・半角カナ(U+FF00-FFEF)、ASCII(U+0020-007E)
unicode-range を書いておくと、そのページに該当する文字が1つもなければブラウザはファイルを取りに行きません。フッターにしか記号が出ないようなケースで効きます。
WordPressでこのCSSを読ませる手順は、子テーマの functions.php から wp_enqueue_style() する形が扱いやすいです。子テーマの組み方はこちらにまとめてあります。
preload でフォントの取得を前倒しする

font-display はあくまで「読み込み中の見せ方」の指定で、読み込みそのものを速くはしません。ちらつく時間を短くしたいなら、取得の開始を早める必要があります。
なぜ遅れて始まるのか
ブラウザがフォントを取りに行くのは、次の順序を踏んだあとです。
CSSの取得が終わるまでフォントのリクエストは始まりません。preload はこの待ちを飛ばし、HTMLのパース中に取得を始めさせる指定です。
書き方
<head> 内の、CSSの読み込みより前に置きます。
<!-- <head> 内。他の <link rel="stylesheet"> より上 -->
<link rel="preload"
href="/assets/fonts/NotoSansJP-Regular-subset.woff2"
as="font"
type="font/woff2"
crossorigin>
<link rel="stylesheet" href="/assets/css/fonts.css">
変更する箇所
hrefのパス —@font-faceのsrcと完全に同じ文字列にする
crossorigin は同一オリジンでも必須です。 フォントのリクエストはCORSモードで飛ぶ仕様なので、これを省くとブラウザは同じファイルを2回取得します。DevToolsのNetworkパネルで同名ファイルが2行並んでいたら、この属性の抜けを疑ってください。
preload するのは1〜2ファイルまで
preload は「他より優先して取れ」という指示です。全部に付ければ優先順位の差がなくなり、むしろファーストビューの画像やCSSの取得を押しのけます。
対象を絞る基準はこうです。
| 対象 | preload するか |
|---|---|
| 本文のRegular | する |
| 見出しのBold | ファーストビューに見出しがあればする |
| Light / Medium など中間ウェイト | しない |
| 斜体 | しない |
| アイコンフォント | ファーストビューにアイコンがなければしない |
迷ったら本文のRegular 1本だけにしてください。表示速度まわりの判断は、入れる数を絞るほど効きます。この考え方はアプリや外部スクリプトでも同じです。
レイアウトの跳ねを size-adjust で消す

ここからが日本語で最も効く部分です。FOUTが目障りに感じられるのは、色や形が変わるからではなく、文字の大きさと行の高さが変わってレイアウトが動くからです。
代替フォント(游ゴシックやヒラギノ、Noto Sans JP)はそれぞれ字面の大きさが違います。同じ font-size: 16px を指定しても、実際に見える文字の大きさは一致しません。結果、入れ替わった瞬間に行数が変わり、下のコンテンツが押し下げられます。
@font-face でフォールバックを定義する
解決策は、代替フォント側を調整して本命フォントとサイズを合わせることです。@font-face に src: local(...) を書き、size-adjust などの記述子で補正します。
/* assets/css/fonts.css の @font-face の後に追記 */
/* 代替フォントを本命に寄せた「調整済みフォールバック」として定義 */
@font-face {
font-family: "NotoSansJP Fallback";
src: local("Hiragino Sans"), local("Yu Gothic"), local("Meiryo");
size-adjust: 100%;
ascent-override: 88%;
descent-override: 12%;
line-gap-override: 0%;
}
/* 本命 → 調整済みフォールバック → 総称 の順で指定する */
body {
font-family: "NotoSansJP", "NotoSansJP Fallback", sans-serif;
}
変更する箇所
src: local(...)— 環境にあるフォント名。Mac(Hiragino Sans)・Windows(Yu Gothic / Meiryo)の順に並べるsize-adjust— 字面の大きさの補正。代替のほうが小さく見えるなら100%より大きくascent-override/descent-override— 行の高さの配分。ここが行送りのズレに効く
補正値の出し方
数値は勘で決めず、実測してください。手順はこうです。
- DevToolsを開き、Networkパネルで Disable cache にチェックを入れる
- 同じパネルのスロットリングを Slow 4G にする
- ページを再読み込みし、代替フォントで表示されている状態でスクリーンショットを撮る
- 読み込み完了後に同じ位置でもう一度撮る
- 2枚を重ねて、本文の1行の高さと段落全体の高さを比べる
段落の高さが代替のほうが低ければ size-adjust を上げ、高ければ下げます。1%刻みで調整すれば数回で合います。
この作業を省くための確認方法もあります。ブラウザのコンソールで次を実行すると、フォント読み込み前後の高さの差が数値で出ます。
// DevTools のコンソールに貼って実行する
// セレクタは自分のサイトの本文要素に変える
(function () {
const target = document.querySelector('.entry-content p');
if (!target) {
console.warn('対象要素が見つかりません。セレクタを確認してください。');
return;
}
const before = target.getBoundingClientRect().height;
console.log('読み込み前の高さ:', before.toFixed(2), 'px');
document.fonts.ready.then(function () {
const after = target.getBoundingClientRect().height;
const diff = after - before;
console.log('読み込み後の高さ:', after.toFixed(2), 'px');
console.log('差分:', diff.toFixed(2), 'px');
if (Math.abs(diff) < 1) {
console.log('ズレはほぼありません。調整不要です。');
} else {
const ratio = (before / after) * 100;
console.log('size-adjust の目安:', ratio.toFixed(1) + '%');
}
});
})();
変更する箇所
.entry-content p— 本文の段落を指すセレクタ。WordPressのブロックテーマなら.wp-block-post-content p、Shopifyなら.rte pなど
差分が1px未満なら調整は不要です。出力された目安の値を size-adjust に入れ、もう一度測って詰めていきます。
size-adjust と ascent-override はChrome 92以降・Firefox 89以降・Safari 17以降で有効です。未対応ブラウザでは記述子が無視されるだけで、フォント自体は正常に表示されます。付けて壊れる指定ではないので、対応状況を待つ必要はありません。
サブセット化でファイルを削る

対策の効き目は、結局のところ読み込みが何秒で終わるかに左右されます。日本語フォントを丸ごと配信している限り、font-display をどう調整しても揺れは残ります。
実務での落としどころ
| 方法 | 削減の度合い | 向いている用途 |
|---|---|---|
| ウェイトを絞る | 大きい | 全サイト共通。RegularとBoldの2つで足りることが多い |
unicode-range で範囲を切る |
中 | 記号・半角カナを別ファイルに分ける |
| 動的サブセット | 最大 | ページ内の文字だけ配信。ビルド工程が要る |
| 本文だけシステムフォント | 最大 | 見出しとロゴだけWebフォントにする |
最後の「本文はシステムフォント」は妥協に見えますが、日本語サイトでは合理的な選択です。ファーストビューが即座に読め、レイアウトも動きません。
/* 本文はシステムフォント、見出しだけWebフォント */
body {
font-family:
"Hiragino Sans",
"Hiragino Kaku Gothic ProN",
"Yu Gothic",
"Meiryo",
sans-serif;
}
h1, h2, h3,
.site-logo,
.section-title {
font-family: "NotoSansJP", "NotoSansJP Fallback", sans-serif;
font-weight: 700;
}
変更する箇所
.site-logo.section-title— Webフォントを適用したい要素のクラス名
見出しだけなら文字数が限られるので、そのページに出る文字だけを収録したサブセットを作れます。ファイルは数十KBまで落とせることが多く、そうなれば swap でも揺れはほとんど見えません。
サブセットの作り方
fonttools の pyftsubset コマンドを使います。Pythonが動く環境なら追加のサービスは不要です。
# インストール(1回だけ)
pip install fonttools brotli
# ひらがな・カタカナ・常用漢字・ASCII に絞って woff2 で出力
pyftsubset NotoSansJP-Regular.ttf \
--output-file=NotoSansJP-Regular-subset.woff2 \
--flavor=woff2 \
--layout-features='*' \
--unicodes="U+0020-007E,U+3000-30FF,U+4E00-9FFF,U+FF00-FFEF"
変更する箇所
- 入力ファイル名 — 手元のフォントファイル
--unicodes— 収録範囲。CSSのunicode-rangeと揃える
brotli を入れておかないと --flavor=woff2 が動きません。--layout-features='*' は約物の詰めなどのOpenType機能を残す指定で、これを省くと日本語の括弧まわりの見た目が変わります。
サブセット化したフォントを再配布する権利があるかは、フォントのライセンスを必ず確認してください。Noto SansやM PLUSなどのSIL Open Font Licenseのフォントは自ホスティングが認められていますが、フォントによって条件が異なります。
ホスト型サービスを使っている場合

Google FontsやAdobe Fonts、TypeSquareのようなサービスでは、@font-face の中身を自分で書き換えられません。打てる手は限られますが、ゼロではありません。
Google Fonts
URLのクエリに &display=swap を付けると font-display を指定できます。値は swap block fallback optional が使えます。
<!-- <head> 内 -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@400;700&display=fallback">
変更する箇所
family=— フォント名とウェイト。wght@400;700の部分で必要なウェイトだけ指定するdisplay=—fallbackまたはoptional
preconnect の2行は、DNS解決とTLSハンドシェイクを先に済ませる指定です。2つ目の crossorigin を省くと効かないので、必ず付けてください。
size-adjust は自分の @font-face として別途定義すれば併用できます。前述の「NotoSansJP Fallback」の定義はそのまま使え、font-family の並び順で本命の後ろに置けば動きます。
自ホスティングへの切り替えを検討する
ホスト型サービスは別ドメインへの接続が発生するため、どうしても初動が遅れます。ウェイトを2つ程度に絞れるなら、フォントファイルを自分のサーバーに置いたほうが速くなることが多い。
判断基準は次の通りです。
- 使うウェイトが3つ以下 → 自ホスティングを検討する
- ライセンスが自ホスティングを認めている → 自ホスティングにする
- 可変フォント(Variable Font)で複数ウェイトを1ファイルにできる → 自ホスティングが有利
- 大量のウェイトやスタイルを使う → サービスのままでよい
Shopifyの場合、テーマのアセットとしてフォントファイルをアップロードでき、asset_url フィルタで参照できます。テーマを直接改修するかアプリで済ませるかの線引きは、こちらで整理しています。
全部をまとめた実装例

ここまでの内容を1セットにまとめます。静的HTMLでもWordPressでもShopifyでも、置き場所が違うだけで中身は同じです。
ファイルの配置
| ファイル | 置き場所(静的HTML) | 置き場所(WordPress子テーマ) | 置き場所(Shopify) |
|---|---|---|---|
| フォント本体 | /assets/fonts/ |
/wp-content/themes/子テーマ/assets/fonts/ |
テーマの assets/ |
| フォント用CSS | /assets/css/fonts.css |
/wp-content/themes/子テーマ/assets/css/fonts.css |
テーマの assets/ |
| preload の記述 | index.html の <head> |
header.php または wp_head フック |
layout/theme.liquid の <head> |
CSS(全文)
/* assets/css/fonts.css */
/* ---- 本命フォント ---- */
@font-face {
font-family: "SiteSans";
src: url("/assets/fonts/SiteSans-Regular.woff2") format("woff2");
font-weight: 400;
font-style: normal;
font-display: fallback;
unicode-range: U+0020-007E, U+3000-30FF, U+4E00-9FFF, U+FF00-FFEF;
}
@font-face {
font-family: "SiteSans";
src: url("/assets/fonts/SiteSans-Bold.woff2") format("woff2");
font-weight: 700;
font-style: normal;
font-display: swap;
unicode-range: U+0020-007E, U+3000-30FF, U+4E00-9FFF, U+FF00-FFEF;
}
/* ---- 調整済みフォールバック ---- */
@font-face {
font-family: "SiteSans Fallback";
src: local("Hiragino Sans"), local("Yu Gothic"), local("Meiryo");
size-adjust: 100%;
ascent-override: 88%;
descent-override: 12%;
line-gap-override: 0%;
}
/* ---- 適用 ---- */
:root {
--font-sans: "SiteSans", "SiteSans Fallback",
"Hiragino Sans", "Yu Gothic", "Meiryo", sans-serif;
}
body {
font-family: var(--font-sans);
font-feature-settings: "palt" 1;
}
h1, h2, h3, h4 {
font-family: var(--font-sans);
font-weight: 700;
}
変更する箇所
"SiteSans"— 実際のフォント名に置き換える(3か所+Fallback定義)srcのパス — 実際の配置場所size-adjustascent-override— 前述のスクリプトで測った値font-feature-settings: "palt" 1— 約物を詰める指定。詰めたくなければこの行を削る
HTML(<head> 内)
<link rel="preload"
href="/assets/fonts/SiteSans-Regular.woff2"
as="font"
type="font/woff2"
crossorigin>
<link rel="stylesheet" href="/assets/css/fonts.css">
WordPressで読み込ませる
子テーマの functions.php に追記します。
<?php
// wp-content/themes/子テーマ/functions.php
// フォント用CSSを読み込む
add_action( 'wp_enqueue_scripts', function () {
wp_enqueue_style(
'site-fonts',
get_stylesheet_directory_uri() . '/assets/css/fonts.css',
array(),
'1.0.0'
);
}, 5 );
// preload を head の早い位置に出す
add_action( 'wp_head', function () {
$font_url = get_stylesheet_directory_uri() . '/assets/fonts/SiteSans-Regular.woff2';
printf(
'<link rel="preload" href="%s" as="font" type="font/woff2" crossorigin>' . "\n",
esc_url( $font_url )
);
}, 1 );
変更する箇所
SiteSans-Regular.woff2— 実際のファイル名'1.0.0'— キャッシュ対策のバージョン。CSSを更新したら上げる
wp_enqueue_scripts の優先度を 5、wp_head を 1 にしているのは、他のスタイルより先に出すためです。テーマによっては他のプラグインが同じフックを使っているので、実際の出力順は表示されたHTMLのソースで確認してください。
Shopifyで読み込ませる
layout/theme.liquid の <head> 内、既存の {{ 'base.css' | asset_url | stylesheet_tag }} より前に置きます。
{%- comment -%} layout/theme.liquid の <head> 内 {%- endcomment -%}
<link rel="preload"
href="{{ 'SiteSans-Regular.woff2' | asset_url }}"
as="font"
type="font/woff2"
crossorigin>
{{ 'fonts.css' | asset_url | stylesheet_tag }}
Shopifyの assets/ に置いたファイルは asset_url フィルタでCDNのURLに変換されます。CSSの中のパスも同様に書き換えが必要なので、fonts.css を fonts.css.liquid にリネームして {{ 'SiteSans-Regular.woff2' | asset_url }} と書くか、@font-face だけを theme.liquid にインラインで書いてください。
Dawnテーマを改修するときの方針は、アップデートで消えない書き方を先に決めておくと後が楽です。
効果を確認する

作業が終わったら、直ったかどうかを目で見て確かめます。
手順
- DevToolsのNetworkパネルで Disable cache をオン
- スロットリングを Slow 4G に設定
- Performanceパネルで記録しながらページを再読み込み
- スクリーンショットのフィルムストリップを1コマずつ送り、文字が入れ替わる瞬間を探す
確認すべきは3点です。
| 見る項目 | 合格の目安 |
|---|---|
| 文字が見えない時間 | 0秒(fallback / optional なら約100ms以内) |
| 入れ替わり時のレイアウトの跳ね | 段落の高さが変わらない |
| フォントの取得開始 | Networkパネルの先頭付近に並ぶ |
Lighthouseの監査でも font-display と preload の指摘は拾えますが、日本語のレイアウトの跳ねは指摘されません。フィルムストリップを目で見る工程は省かないでください。
CLS(Cumulative Layout Shift)の数値にもフォントの入れ替わりは乗ります。Search Consoleのウェブに関する主な指標で急に悪化したときは、フォントまわりの変更履歴を確認する価値があります。
うまくいかないとき

preload しているのに2回ダウンロードされる
crossorigin 属性が抜けています。同一オリジンでも必要です。
もう1つの原因は、preload の href と @font-face の src の文字列が一致していないことです。片方が絶対URL、もう片方が相対パスだと別物として扱われます。DevToolsのConsoleに「preloaded but not used」という警告が出ていたら、この2つを見比べてください。
size-adjust を書いても何も変わらない
3つの可能性があります。
- フォールバックの
@font-faceが使われていない —font-familyの並びに"◯◯ Fallback"が入っているか確認する src: local(...)のフォント名が環境にない — Macでlocal("Yu Gothic")だけ書いても見つからない。複数書いて並べる- ブラウザが未対応 — Safari 17より前では無視される
確認は document.fonts.check('16px "SiteSans Fallback"') をコンソールで実行してください。true が返れば認識されています。
フォントが適用されない(読み込みはされている)
@font-face の font-weight と、要素に指定した font-weight が一致していない可能性があります。@font-face で font-weight: 400 としたフォントに font-weight: 500 を当てると、ブラウザが合成(擬似的に太くする)することがあり、意図した見た目になりません。
もう1つ多いのが unicode-range の指定ミスです。範囲外の文字はフォールバックで描画されるため、「漢字だけ違うフォント」のような状態になります。表示がおかしい文字をコピーして、その文字コードが unicode-range に含まれるか確認してください。
ローカルでは直ったのに本番で揺れる
サーバーの配信設定を確認します。
Content-Typeが正しくない —.woff2はfont/woff2。application/octet-streamで返っていると一部ブラウザが拒否する- キャッシュヘッダが短い —
Cache-Control: public, max-age=31536000, immutableを設定する。リピーターの揺れが消える - 別ドメインから配信している — CORSヘッダ(
Access-Control-Allow-Origin)が必要
Apacheなら .htaccess に次を追記します。
# .htaccess
<IfModule mod_mime.c>
AddType font/woff2 .woff2
</IfModule>
<IfModule mod_expires.c>
ExpiresActive On
ExpiresByType font/woff2 "access plus 1 year"
</IfModule>
<IfModule mod_headers.c>
<FilesMatch "\.woff2$">
Header set Cache-Control "public, max-age=31536000, immutable"
</FilesMatch>
</IfModule>
変更する箇所: なし。そのまま追記して動きます。既に同じディレクティブがある場合は重複を避けてください。
optional にしたらWebフォントが全く出ない
optional はスワップ期間がないため、初回訪問では代替フォントのまま確定することが多い値です。2回目以降はキャッシュから即座に読めるので本命フォントが出ます。
初回でも出したいなら fallback に変えるか、preload と組み合わせてください。optional + preload は「取得を最優先しつつ、間に合わなければ諦める」という組み合わせで、CLSを最小にしたいときに有効です。
管理画面やプレビューだけ挙動が違う
WordPressの管理画面は別のスタイルシートで動いているため、フロント側の @font-face は効きません。ブロックエディタ内の表示を揃えたい場合は add_editor_style() で別途読み込ませます。
納品前に管理画面まわりを整える手順は、こちらにまとめてあります。
よくある質問

font-display は swap にしておけば問題ないですか
欧文中心のサイトなら swap で十分ですが、日本語では慎重に選んでください。swap はスワップ期間が無制限なので、読者が読み始めた何秒も後に文字が入れ替わり、レイアウトが動く可能性があります。本文には fallback か optional、見出しやロゴには swap という使い分けが現実的です。
preload はいくつまで付けていいですか
1〜2ファイルに絞ってください。preload は優先順位を上げる指示なので、全部に付けると差がなくなり、ファーストビューの画像やCSSの取得を遅らせます。本文のRegular 1本から始めて、フィルムストリップで効果を確認しながら増やすか判断するのが安全です。
Google Fonts と自ホスティング、どちらが速いですか
使うウェイトが3つ以下でライセンスが許すなら、自ホスティングのほうが速くなることが多いです。別ドメインへのDNS解決とTLSハンドシェイクが不要になり、preload も効かせやすくなります。ウェイトやスタイルを多数使う場合は、管理の手間を考えるとサービスのままでも構いません。
size-adjust の値はどうやって決めればいいですか
実測してください。DevToolsでキャッシュを無効化し、Slow 4Gで読み込み前後のスクリーンショットを撮って段落の高さを比べます。本文中に載せたJavaScriptをコンソールで実行すれば、高さの差分と size-adjust の目安が数値で出ます。1%刻みで詰めれば数回で合います。
サブセット化したフォントを自分のサーバーに置いていいですか
フォントのライセンス次第です。SIL Open Font Licenseのフォント(Noto Sans JP、M PLUS など)はサブセット化と自ホスティングが認められていますが、商用フォントの多くはWebフォントとしての利用に別途契約が必要で、サブセット化を禁じている場合もあります。作業前に必ずライセンス文書を確認してください。
まとめ
日本語サイトのフォントのちらつきは、3つの層で対処します。
| 層 | やること | 効き目 |
|---|---|---|
| 見せ方 | font-display を用途別に指定する |
ちらつきの性質を変える |
| 取得のタイミング | preload を1〜2本だけ付ける |
ちらつく時間を短くする |
| サイズの整合 | size-adjust でフォールバックを合わせる |
レイアウトの跳ねを消す |
このうち体感に最も効くのは3つ目です。文字の形が変わることより、行が動いて読んでいた場所を見失うことのほうが不快に感じられます。時間が限られているなら、size-adjust の調整から手を付けてください。
そして根本の解決はファイルを小さくすることです。ウェイトを2つに絞り、必要な文字だけのサブセットを作る。あるいは本文はシステムフォントに任せ、見出しとロゴだけWebフォントにする。この判断ができれば、残りの調整は数十分で終わります。





