コーポレートサイトの SEO チェックで、PageSpeed Insights(デスクトップ)に「レンダリングをブロックしているリクエスト」が出ました。推定される削減時間は 2,000ms です。
ユーザー補助・おすすめの方法・SEO はすでに 100 になっていたので、残るパフォーマンスに取り組みました。結果は次のとおりです。
| 指標 | 作業前 | 作業後(最良値) |
|---|---|---|
| パフォーマンス | 55 | 76 |
| First Contentful Paint | 2.1秒 | 1.2秒 |
| Largest Contentful Paint | 3.4秒 | 2.8秒 |
| Total Blocking Time | 250ms | 50ms |
| Speed Index | 4.2秒 | 2.6秒 |
| Cumulative Layout Shift | 0.002 | 0.002 |
この記事では、効いた施策だけでなく、効かなかった施策と逆効果だった施策も、試した順に残します。前回までの施策は「PageSpeed Insightsスコアを改善」「WordPressのLCP・TBTを改善した施策」「WordPressのCLSを改善」にまとめています。今回はその続きです。
Webパフォーマンス改善:PageSpeed Insightsスコアを大幅に改善できた5つの施策 | CODE-PLUS(コードプラス)
施策1:JS に defer を付ける(効果:小)
やったこと
defer を付けた JS は HTML の解析を止めず、解析が終わってから書いた順に実行されます。ライブラリ同士の実行順は変わりません。
defer にしてよいかは、ライブラリがいつ動き始めるかで決まります。CDN のファイルを1本ずつ開いて確かめました。
| ライブラリ | 動き始めるタイミング | 判断 |
|---|---|---|
| response-lazy(画像の出し分け) | jQuery の ready、DOMContentLoaded | defer にする |
| スクロールアニメーション | DOMContentLoaded | defer にする |
| アコーディオン | DOMContentLoaded | defer にする |
| Swiper | 初期化は window の load で行っている | defer にする |
| GSAP・ScrollTrigger | DOMContentLoaded の後に refresh を呼ぶだけ | defer にする |
| jQuery | 本文のインラインスクリプトに $(function(){…}) がある |
そのまま |
defer の JS は DOMContentLoaded より前に実行されるので、DOMContentLoaded や load を待って動くコードはそのまま動きます。一方、本文の途中に $(function(){…}) が直接書かれていると、その時点で jQuery が無いとエラーになります。jQuery だけは defer にできません。
<!-- jQuery:本文のインラインスクリプトが使うので defer にしない -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
<!-- DOMContentLoaded で動くライブラリは defer -->
<script src="https://cdn.jsdelivr.net/npm/@sarap422/response-lazy@0.10.5/response.min.js" defer></script>
<script src="https://cdn.jsdelivr.net/npm/@sarap422/lib-scrollin@1.3.0/scrollin.min.js" defer></script>
あわせて、次の2つも読み込みをやめました。
- jquery-migrate(5.6KiB)。全ページのインラインスクリプトとテーマの JS を調べ、
.bind(や.size(、$.isArray(のような古い書き方が無いことを確かめた - 中身が 0 バイトの critical.min.css。空でもファイルの到着を待つので、コメントアウトした
結果
推定削減時間は 2,000ms から 1,850ms になっただけでした。
JS が一覧から消えても、CSS が19本残っています。ブラウザは CSS がそろうまで画面を描かないので、JS だけ外しても表示は早くなりません。
施策2:CSS を全部非同期にする(逆効果)
やったこと
CSS を非同期で読み込む定番の書き方は、media="print" で読み込んでおき、読み込みが終わったら all に切り替える方法です。印刷用の CSS は表示を止めないので、画面の描画を待たせずに済みます。
<link rel="stylesheet" href="style-utilities.min.css" media="print" onload='this.media="all"' />
<!-- JS が無効な環境向け -->
<noscript>
<link rel="stylesheet" href="style-utilities.min.css" />
</noscript>
フォントの CSS 6本と、テーマの CSS 4本(カラーパレット2本、コンポーネント、ユーティリティ)に、この書き方を使いました。
結果
| 指標 | 施策1の後 | CSS を全部非同期にした後 |
|---|---|---|
| パフォーマンス | 55 | 50〜51 |
| First Contentful Paint | 2.1秒 | 0.6秒 |
| Total Blocking Time | 250ms | 1,690〜1,810ms |
| Speed Index | 4.2秒 | 5.0〜5.3秒 |
| Cumulative Layout Shift | 0.002 | 0.041〜0.043 |
レンダリングブロックの推定削減時間は 160ms まで減り、FCP も 0.6秒に縮みました。それなのにスコアは下がっています。TBT が約7倍になったためです。
TBT が増えた理由
手元の Lighthouse でメインスレッドの内訳を見ると、一番重いのは「Style & Layout」(約1.5秒)でした。理由は2つ考えられます。
1つ目は、TBT の数え方です。TBT は FCP から後の重い処理だけを数えます。FCP が 2.1秒から 0.6秒に早まった分、前は FCP より前にあって数えられていなかった処理が、TBT に入るようになりました。処理そのものが増えたわけではありません。
2つ目は、非同期の CSS が届くたびに、ページ全体のスタイルを計算し直すことです。テーマの CSS は圧縮前で約740KB あり、それが4回に分かれて届くので、そのたびに計算し直しが走ります。CLS が 0.04 まで上がったのも、CSS が後から当たってレイアウトが動いたためです。
施策3:テーマの CSS を戻し、アイコンの CSS だけ非同期にする(効果:大)
やったこと
テーマの CSS とフォントの CSS は、元の読み込み方に戻しました。非同期のままにしたのは、アイコンフォントの CSS 2本だけです。
アイコンの CSS(material-symbols.min.css)は、3,400個以上のアイコンの指定を持つ約209KB(転送 29.7KiB)のファイルです。全ページを調べると、サイトで使っているアイコンは6個だけでした。少し遅れて出ても困らないので、非同期にしました。
<!-- アイコン:非同期 -->
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Sharp:wght,FILL@100..700,0..1" media="print" onload='this.media="all"' />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@sarap422/font-material-symbols@1.0.0/material-symbols.min.css" media="print" onload='this.media="all"' />
<noscript>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Sharp:wght,FILL@100..700,0..1" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@sarap422/font-material-symbols@1.0.0/material-symbols.min.css" />
</noscript>
<noscript> の中には media="print" onload を付けません。<noscript> は JS が無効な環境でしか使われず、onload が動かないので、付けると CSS が適用されないままになります。
結果
| 指標 | CSS を全部非同期にした後 | アイコンだけ非同期 |
|---|---|---|
| パフォーマンス | 51 | 69 |
| First Contentful Paint | 0.6秒 | 1.2秒 |
| Largest Contentful Paint | 1.9秒 | 2.7秒 |
| Total Blocking Time | 1,810ms | 190ms |
| Speed Index | 5.3秒 | 3.3秒 |
| Cumulative Layout Shift | 0.043 | 0.002 |
FCP は 0.6秒から 1.2秒に戻りましたが、TBT と CLS が元の水準に戻り、スコアは 69 になりました。推定削減時間は、施策1の後の 1,850ms から 1,030ms に減っています。
CSS は、ページの形を決めるものは先に読み込み、遅れても見た目が崩れないもの(アイコン、印刷用、装飾用のフォント)だけを非同期にする、という線引きに落ち着きました。
施策4:使っていない GSAP を外す(効果:大)
やったこと
手元の Lighthouse で長いタスクを見ると、上位に GSAP と ScrollTrigger が並んでいました(189ms、113ms、77ms、68ms)。
トップページの本文と、トップで読み込むテーマの JS を調べると、GSAP を使っている箇所はありませんでした。使っていたのは、画像が読み込まれるたびに ScrollTrigger.refresh() を呼ぶ処理だけです。この refresh がページ全体のレイアウトを計算し直すので、使っていないのに重い処理だけが残っていました。
<?php if (is_front_page()) : ?>
<!-- GSAP:トップでは使っていないので読み込まない -->
<!-- <script src="https://cdn.jsdelivr.net/npm/gsap@3.13.0/dist/gsap.min.js" defer></script> -->
<!-- <script src="https://cdn.jsdelivr.net/npm/gsap@3.13.0/dist/ScrollTrigger.min.js" defer></script> -->
<?php endif; ?>
refresh を呼ぶための imagesLoaded も、全ページで読み込んでいました。こちらは GSAP を読み込むテンプレートだけで出すようにしました。
<?php if (is_page_template('templates/page-equipment.php')) : ?>
<script src="https://unpkg.com/imagesloaded@4/imagesloaded.pkgd.min.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
if (!window.ScrollTrigger) return;
imagesLoaded(document.querySelectorAll('img'), function() {
ScrollTrigger.refresh();
});
});
</script>
<?php endif; ?>
imagesLoaded は、masonry やモーダル用のスライダーのパーツでも使っています。全ページを調べて、どのページにも置かれていないことを確かめてから条件を付けました。
結果
| 指標 | アイコンだけ非同期 | GSAP を外した後 |
|---|---|---|
| パフォーマンス | 69 | 76 |
| Total Blocking Time | 190ms | 50ms |
| Speed Index | 3.3秒 | 2.6秒 |
今回の施策の中では、これが一番効きました。テンプレートを使い回していると、ページによっては使わないライブラリも一緒に読み込まれがちです。ページごとに本当に使っているかを確かめる価値はあります。
施策5:ホワイトインを短くする(効果:なし)
やったこと
読み込み途中の画面(フィルムストリップ)を見ると、約2.7秒まで画面が真っ白でした。原因は、フォントの切り替わりやヘッダーのちらつきを隠すための白いオーバーレイです。初めて来た人には DOMContentLoaded の1秒後から、1.5秒かけて消える作りでした。
PageSpeed Insights は毎回「初めて来た人」として計るので、この演出の時間がそのまま Speed Index に乗ります。そこで、次のように短くしました。
| 箇所 | 前 | 後 |
|---|---|---|
| オーバーレイが消え始めるまでの待ち(初回訪問) | 1秒 | 0.5秒 |
| オーバーレイが消える時間 | 1.5秒 | 1秒 |
| ヘッダーのフェードイン(待ち/かける時間) | 1秒/1秒 | 0.5秒/0.6秒 |
結果
2回計ってスコアは 55 と 71 で、効いたのかどうか判別できませんでした。
計測の揺れについて
ホワイトインを短くした後は、同じ状態のまま10分おきに計って、55 と 71 でした。16ポイントの差は、施策1つ分の効果より大きいことがあります。1回の計測で良し悪しを決めず、施策ごとに2回以上計ることをおすすめします。
XMRGRTX
一方、実際の利用者のデータ(CrUX、過去28日間)は、作業の前から LCP 1.3秒、INP 42ms、CLS 0.04 で、すべて「良好」でした。Google の評価に使われるのはこちらです。PageSpeed Insights のスコアは、改善箇所を探すための模擬計測として見るのがよいと思います。
残った課題
| 項目 | 内容 | 今回やらなかった理由 |
|---|---|---|
| 画像配信を改善する | 推定 1,805KiB | 画像を小さくすることは許可されていない |
| スライダーの画像 | 1枚目(LCP の画像)には fetchpriority="high" を付けているが、2〜8枚目もすぐに読み込まれ、回線を取り合っている |
Swiper のフェード効果では全スライドが同じ位置に重なるので、loading="lazy" では遅らせられない |
| テーマの CSS | 「使用していない CSS」205KiB の大半がユーティリティとコンポーネント | 投稿本文で使っているクラスを洗い出す必要があり、別の作業になる |
| キャッシュ保存期間 | 推定 305KiB | 2回目以降の訪問にしか効かず、スコアには入らない |
ここから先は、画像そのものに手を入れるか、テーマの CSS を作り直すかの段階になるので、今回の見直しはここで区切りました。
まとめ
| 施策 | 結果 | スコア |
|---|---|---|
| JS に defer を付ける | CSS が残っているので効果は小さい | 55 |
| CSS を全部非同期にする | FCP は縮むが、TBT が約7倍になり逆効果 | 50〜51 |
| アイコンの CSS だけ非同期にする | TBT と CLS が戻る | 69 |
| 使っていない GSAP を外す | TBT 190ms → 50ms。効果:大 | 76 |
| ホワイトインを短くする | 計測の揺れに埋もれて判別できず | 55〜71 |
今回わかったのは、次の3つです。
- 「レンダリングをブロックしているリクエスト」の数字だけを減らしても、スコアは上がらない。ページの形を決める CSS まで非同期にすると、TBT と CLS が悪化する
- 一番効果が大きいのは、使っていないライブラリを外すことだった。読み込み方を工夫するより、読み込まないほうが効く
関連記事
Webパフォーマンス改善:PageSpeed Insightsスコアを大幅に改善できた5つの施策 | CODE-PLUS(コードプラス)