Webフォント(Google Fonts)の読み込みを描画を止めない形に変えて、モバイルのLCPを約2秒にした記録
Google Fontsの
この<link rel="stylesheet">で読むと、display=swapを<link rel="preload" as="style" onload="this.onload=null;this.rel='stylesheet'">と<noscript>の
このsrc/layouts/Base.astroの
何が描画を止めていたか
止めていたのは、
変更する<head>は、display=swapは
<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=IBM+Plex+Mono:wght@400&family=Noto+Sans+JP:wght@400;700&family=Noto+Serif+JP:wght@600&display=swap" />display=swapは、font-displayの@font-faceを
この@font-faceの
変えたのは3行:preloadで取りに行き、届いたら当てる
CSSはpreloadでrelをstylesheetに<noscript>にも
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<!-- Webフォントは描画を止めないよう後から適用する(文字は先に端末のフォントで出し、届いたら切り替える) -->
<link rel="preload" as="style" href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400&family=Noto+Sans+JP:wght@400;700&family=Noto+Serif+JP:wght@600&display=swap" onload="this.onload=null;this.rel='stylesheet'" />
<noscript><link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400&family=Noto+Sans+JP:wght@400;700&family=Noto+Serif+JP:wght@600&display=swap" /></noscript>それぞれの
| 部分 | 役割 |
|---|---|
rel="preload" as="style" |
CSSを |
onload="...this.rel='stylesheet'" |
届いたら |
this.onload=null |
rel を |
<noscript> |
JavaScriptが |
preconnect(2行) |
変更前からcrossorigin を付ける |
このloadCSSのようなonloadのscript-srcを
測り直した結果:LCPは約2秒、CLSは変わらず
変更の
測ったのは、/techacademy/)の
| ページ | 回 | 変更前の |
変更前の |
変更後の |
変更後の |
|---|---|---|---|---|---|
| トップ | 1 | 86 | 2.1秒 / 3.8秒 | 99 | 1.4秒 / 1.9秒 |
| トップ | 2 | 89 | 2.1秒 / 3.4秒 | 99 | 1.4秒 / 1.9秒 |
| トップ | 3 | 89 | 2.1秒 / 3.5秒 | 99 | 1.4秒 / 1.9秒 |
| 記事 | 1 | 84 | 2.4秒 / 4.0秒 | 98 | 1.7秒 / 2.0秒 |
| 記事 | 2 | 87 | 2.4秒 / 3.5秒 | 99 | 1.7秒 / 1.9秒 |
| 記事 | 3 | 87 | 2.4秒 / 3.6秒 | 99 | 1.7秒 / 2.0秒 |
CLSは、
この
サイトの
本番での数字と、1回目だけ悪く出る測定
デプロイした
本番の
何が速くなったのか:LCPの写真が描画を待たなくなった
LCPの
手元の
Lighthouseの
引き換えになったこと:文字の切り替わりと、ずれの確かめ方
引き換えに、
切りdisplay=swapで
書体を
| ページ | 変更前 | 変更後 |
|---|---|---|
| トップ | サイト名の |
トップの |
| 記事 | 記事の |
記事の |
どれも、
--font: 'Hiragino Sans', 'Hiragino Kaku Gothic ProN', 'Noto Sans JP', 'BIZ UDPGothic', Meiryo, system-ui, sans-serif;
--serif: 'Noto Serif JP', 'Hiragino Mincho ProN', 'Yu Mincho', 'YuMincho', serif;
--mono: 'IBM Plex Mono', ui-monospace, 'SFMono-Regular', Menlo, Consolas, monospace;裏をsize-adjustなどでdisplay=optionalで
フォントを自分のサーバーに置かなかった理由
自分の
Google Fontsは、unicode-rangeの
速さの<head>の
Google Fontsのtext=の
測り方:変更前後のコミットをビルドして、同じ条件で並べる
変更の
# 変更前(0be5f54 の1つ前)と変更後(0be5f54)を、別のフォルダに取り出してビルドする
git worktree add --detach ../wt-before 0be5f54^
git worktree add --detach ../wt-after 0be5f54
for w in before after; do
(cd ../wt-$w && ln -s "$OLDPWD/node_modules" node_modules && npx astro build)
done
# それぞれの dist を別のポートで配る
(cd ../wt-before/dist && python3 -m http.server 8101) &
(cd ../wt-after/dist && python3 -m http.server 8102) &
# 3回ずつ、トップページと記事ページを Lighthouse(既定のモバイルの設定)で測る
mkdir -p lh
for i in 1 2 3; do
for v in before:8101 after:8102; do
name=${v%%:*}; port=${v##*:}
for pg in home: techacademy:techacademy/; do
pn=${pg%%:*}; up=${pg#*:}
npx lighthouse "http://localhost:$port/$up" --quiet --chrome-flags="--headless=new" \
--only-categories=performance --output=json --output-path="lh/$name-$pn-$i.json"
done
done
done結果の
// lh/ にある Lighthouse の JSON から、点数・FCP・LCP・CLS・描画を妨げるリソースを並べる
import fs from 'node:fs';
for (const f of fs.readdirSync('lh').filter((f) => f.endsWith('.json')).sort()) {
const r = JSON.parse(fs.readFileSync(`lh/${f}`, 'utf8'));
const a = r.audits;
const blocking = (a['render-blocking-resources']?.details?.items ?? [])
.map((i) => `${new URL(i.url).host}${new URL(i.url).pathname.slice(0, 20)} ${Math.round(i.wastedMs)}ms`)
.join(', ');
console.log(
f.replace('.json', ''),
Math.round(r.categories.performance.score * 100),
'FCP', a['first-contentful-paint'].displayValue,
'LCP', a['largest-contentful-paint'].displayValue,
'CLS', a['cumulative-layout-shift'].displayValue,
'|', blocking || '(なし)',
);
}python3 -m http.serverは
表示の
株式会社bundlyzeでは、
動作確認した環境
確認日は
| 項目 | バージョン・内容 |
|---|---|
| OS | macOS 26 |
| Node.js | 22.23.2 |
| Astro | 7.3.5 |
| Lighthouse | 12.8.2 |
| ブラウザ | Google Chrome 154 |
| 手元の |
Python 3.9.6 のhttp.server |
| 測ったもの | コミット0be5f54のdist(3回ずつ)と、 |
Androidや<noscript>の
参照した公式ドキュメント(確認日:2026年10月4日)
- web.dev
「重要でない :CSS を 後回しに する」 preloadとonloadで後から stylesheetに 切り 替える 書き方、 this.onload=nullの理由、<noscript>での代わり、 本番では loadCSSのような 関数を 勧める こと、 CSPとの 衝突 - web.dev
「フォントに :CSSを関する おすすめの 方法」 読み終えるまで フォントが 要るか 分からない こと、 preconnectで 別々の ドメインに 接続しておく こと、 unicode-rangeでの分割、 自分で 置く 場合との 差は はっきりしない こと、 font-display: swapの性質、 フォントの 指定を <head>に直接書く こと - web.dev
「CLS を :Webフォントの最適化する」 切り 替わりで ずれが 起きる こと、 font-display: optional、size-adjustなどで代わりの 書体を 寄せる こと - MDN「rel=preload」:preloadは
取りに 行って キャッシュするだけで 適用しない こと、 as="style"、フォントは 匿名の CORSで 取りに 行く こと - Google Fonts「CSS API update」:
displayの指定でfont-displayを決める こと、 text=で使う 文字だけを 渡すと ファイルを 小さく できる こと、 使う 太さを 絞る こと
よくある質問
URLに display=swap を付けていれば、Google Fontsは描画を止めないのではありませんか?
止めます。
後からフォントを当てると、画面がずれてCLSが悪くなりませんか?
ずれは
JavaScriptを切っている人には、フォントが当たらなくなりますか?
当たります。
フォントを自分のサーバーに置いたほうが速くなりませんか?
必ずしも