はじめに
「WordPress高速化」シリーズの第10回です。PageSpeed Insights(PSI)で記事を計測すると、「DOM サイズを最適化する」という項目が表示されることがあります。以前は「過大なDOMサイズの回避」という名前だった項目です。当サイトの記事で、どこが要素を増やしているのかを数えてみました。
結果は次のとおりです。
- SWELLでは、グローバルナビ(グロナビ)が、ヘッダー・追従ヘッダー・スマホメニューの3か所に出力されていて、ページの要素の約3分の1を占めていた
- 見えていない部分も数に入る。スマホでは、PC用のグロナビと追従ヘッダーの約350要素が、表示されないまま数えられていた
- 本文には手を付けずに、メニューの出し方を変えるだけで、要素数が1,232→924(約25%減)になった
この記事では、内訳の数え方と、SWELLでメニューの重複を減らす3つの方法を解説します。
WordPress 7.1.4、SWELL 2.19、Chrome 156 で確認しています。対策2の方法Bでは、SWELLの子テーマにファイルを追加し、functions.php を編集します。編集する前に、必ずバックアップを取ってください。
「DOM サイズを最適化する」とは
DOMは、ブラウザがHTMLを読み込んで作る、ページの部品(要素)の集まりです。<div> や <li>、<a> の1つ1つが要素として数えられます。

PSIでは、「インサイト」の中に表示され、次の3つの数字が出ます。
| 項目 | 意味 | 上の画像の記事(PC) |
|---|---|---|
| 合計要素数 | ページにある要素の数 | 1,647 |
| DOM の深さ | 要素が何重に入れ子になっているか(最も深いところ) | 21 |
| 子の最大数 | 1つの要素の直下にある要素の数(最も多いところ) | 149(コードブロック) |
上の画像では、子の最大数がVBAのコードブロックになっています。コードの色分けは、単語ごとに <span> で囲んで色を付けるため、長いコードほど直下の要素が増えます。DOM の深さについては、Googleの公式ドキュメントでも、深いこと自体は問題ではなく、不要な入れ子が多いことの目印として見るものとされています。
要素が多いと、ブラウザがスタイルを計算したり、配置(レイアウト)を計算し直したりする時間が長くなり、クリックしたときの反応が遅くなることがあります。メモリの使用量も増えます。
ただし、この項目は「スコアなし」と表示されていて、パフォーマンスのスコアに直接は加算されません。要素を減らしたからといって、スコアが大きく上がるわけではありません。
以前の基準と今の基準
PSIの計測に使われているLighthouseには、以前は「過大なDOMサイズの回避」という項目があり、bodyの中の要素が約800を超えると注意、約1,400を超えるとエラーになっていました。ネットで見かける「800」「1,400」という数字は、この基準です。
Lighthouse 13で、この項目は今の「DOM サイズを最適化する」にまとめられ、基準も変わりました。今は要素数そのものに基準の数字はなく、1回のスタイルやレイアウトの計算に40msを超える時間がかかったときに、警告として表示されます(公式ドキュメント:DOM サイズを最適化する)。要素が多いと、この計算が重くなりやすくなります。
見えていない要素も数に入る
注意したいのは、display:none で隠した要素も数に入ることです。SWELLの「PCのみ表示」「SPのみ表示」も、CSSで隠しているだけなので、要素はページに残っています。PCとスマホで別々の部品を用意して片方を隠す作り方は、両方の分だけ要素が増えます。
反対に、<iframe> の中身は数に入りません。広告や埋め込みの動画・SNSの投稿は、iframeの中に別のページとして読み込まれるので、元のページの要素数には含まれません。
内訳を数える:どこが要素を増やしているか
PSIでわかるのは合計だけなので、場所ごとの内訳を検証ツールで数えます。記事を開いて、検証ツール(F12)の「Console」タブで次のコードを実行します。
// SWELLのページで、場所ごとの要素数を数える
const count = (s) => {
const el = document.querySelector(s);
return el ? el.querySelectorAll('*').length + 1 : 0;
};
console.table({
'合計': document.body.querySelectorAll('*').length,
'ヘッダー': count('#header'),
'追従ヘッダー': count('#fix_header'),
'スマホメニュー': count('#sp_menu'),
'本文': count('.post_content'),
'記事下': count('#after_article'),
'サイドバー': count('#sidebar'),
'フッター': count('#footer'),
});querySelectorAll('*').length:その要素の中にある、すべての要素の数です。自分自身の分として1を足しています#headerなど:SWELLが出力する、それぞれの場所のIDですconsole.table:結果を表の形で表示します
合計の数は、PSIの値と少しずれることがあります。PSIとは数えるタイミングが違うためです。どこが多いかを見るには、この数え方で十分です。
当サイトの結果
当サイトの記事4本を、ヘッドレスChromeで数えた結果です。
| ページ | スマホ | PC |
|---|---|---|
| トップページ | 867 | 867 |
| VBAマクロの連続実行の記事 | 1,286 | 1,367 |
| Power Queryのソース変更の記事 | 1,185 | 1,260 |
| この前の第9回の記事 | 1,458 | 1,532 |
第9回の記事(スマホ)の内訳は、次のとおりです。
| 場所 | 要素数 | スマホで見えるか |
|---|---|---|
| ヘッダー(うちグロナビ173) | 187 | グロナビは見えない |
| 追従ヘッダー | 178 | 見えない |
| スマホメニュー | 132 | 閉じている |
| 本文 | 520 | 見える |
| 記事下(著者情報・関連記事5件) | 115 | 見える |
| サイドバー | 134 | 見える |
本文以外の部分は、どの記事でもほぼ同じ数でした。本文が長い記事ほど、合計が増えていきます。
グロナビが3か所に出力されている
内訳を詳しく見ると、ヘッダーのグロナビ、追従ヘッダー、スマホメニューの中身は、どれも同じメニューでした。当サイトのグロナビは、親項目6つ・全47項目です。それが3回出力されて、合わせて約480要素になっていました。

SWELLのテーマのファイルを読むと、それぞれ次の理由で出力されています。
| 場所 | 出力される理由 |
|---|---|
| ヘッダーのグロナビ | 「グローバルナビ」に割り当てたメニュー。スマホでは隠している |
| 追従ヘッダー | カスタマイザーの「ヘッダーを追従させる(PC)」がオンのとき、グロナビをもう一度出力する。スマホでは隠している |
| スマホメニュー | 「スマホ開閉メニュー内」にメニューが割り当てられていないと、グロナビを代わりに出力する |
追従ヘッダーは、JavaScriptで後から作られるのではありません。サーバーが最初から出力するHTMLに入っています。スマホでは、PC用のグロナビと追従ヘッダーの両方が隠れたまま残っていて、約350要素が表示されずに数えられていました。
手順:メニューの重複を減らす
Localに、当サイトと同じグロナビ(47項目)と、第9回と同じ本文のテスト記事を用意して、対策ごとの要素数を数えました。対策前は 1,232 です。
対策1:スマホメニュー用の短いメニューを作る
スマホメニューに、グロナビとは別の短いメニューを割り当てます。
- 外観 > メニュー で「新しいメニューを作成しましょう」をクリックし、スマホ用のメニューを作る(例:親カテゴリーだけの6項目)
- 「メニュー設定」の「メニューの位置」で 「スマホ開閉メニュー内」 にチェックを入れて、保存する
スマホメニューの要素は129→22になり、合計は 1,232→1,125(−107) になりました。スマホでは、メニューを開いたときに長い一覧が出なくなるので、読者にとっても選びやすくなります。
対策2:追従ヘッダーのグロナビを減らす
方法は2つあります。
方法A:PCの追従ヘッダーをオフにする
外観 > カスタマイズ > ヘッダー > ヘッダーの追従設定 の 「ヘッダーを追従させる(PC)」 のチェックを外します。追従ヘッダーが出力されなくなり、合計は 1,125→949(−176) になりました。
ただし、PCでスクロールしたときに、ページの上部にメニューが付いてこなくなります。スマホの追従(「ヘッダーを追従させる(SP)」)は別の設定なので、影響しません。
方法B:追従は残し、グロナビを表示するときにコピーする
PCの追従ヘッダーを残したい場合は、追従ヘッダーのグロナビを最初は出力せず、PCで追従ヘッダーが初めて表示されるときに、ヘッダーのグロナビをJavaScriptでコピーします。
まず、子テーマ(swell_child)に parts/header/fix_header.php というファイルを作り、次の内容を貼り付けます。フォルダ parts と header がない場合は作成してください。SWELLは、子テーマに同じ名前のファイルがあると、そちらを使います。
<?php
if ( ! defined( 'ABSPATH' ) ) exit;
// 追従ヘッダー:グロナビは出力せず、表示されるときにJavaScriptでコピーする
?>
<div id="fix_header" class="l-fixHeader <?=esc_attr( SWELL_Theme::get_header_class() )?>">
<div class="l-fixHeader__inner l-container">
<div class="l-fixHeader__logo">
<?php echo SWELL_PARTS::head_logo( true ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
</div>
<div class="l-fixHeader__gnav c-gnavWrap"></div>
</div>
</div>SWELL本体の parts/header/fix_header.php から、グロナビを出力する部分だけを取り除いたものです。ロゴと、グロナビを入れる空の枠は残しています。
次に、子テーマの functions.php に、次のコードを追加します。
// 追従ヘッダーが初めて表示されるときに、グロナビをコピーする(PCのみ)
add_action('wp_footer', function () {
if (!SWELL_Theme::get_setting('fix_header')) return;
?>
<script>
(() => {
const gnav = document.querySelector('#gnav .c-gnav');
const target = document.querySelector('.l-fixHeader__gnav');
if (!gnav || !target || target.children.length) return;
const pc = window.matchMedia('(min-width: 960px)');
const copy = () => {
if (target.children.length || !pc.matches) return;
if (document.documentElement.getAttribute('data-scrolled') !== 'true') return;
const clone = gnav.cloneNode(true);
// 検索ボタンなどは、元のボタンのクリックとして動かす
const src = gnav.querySelectorAll('[data-onclick]');
clone.querySelectorAll('[data-onclick]').forEach((btn, i) => {
btn.addEventListener('click', (e) => { e.preventDefault(); src[i].click(); });
});
target.appendChild(clone);
observer.disconnect();
};
const observer = new MutationObserver(copy);
observer.observe(document.documentElement, { attributes: true, attributeFilter: ['data-scrolled'] });
pc.addEventListener('change', copy);
})();
</script>
<?php
}, 99);SWELL_Theme::get_setting('fix_header'):「ヘッダーを追従させる(PC)」がオフのときは、何もしませんdata-scrolled:SWELLは、ページをスクロールしてヘッダーが画面から外れると、<html>のdata-scrolledを"true"にして、追従ヘッダーを表示します。この変化をMutationObserver(要素の変化を見張る仕組み)で待ちますmatchMedia('(min-width: 960px)'):画面幅が960px以上(SWELLのPC表示)のときだけコピーします。スマホでは追従ヘッダーにグロナビを使わないので、コピーしませんcloneNode(true):ヘッダーのグロナビを、中身ごとコピーします[data-onclick]:グロナビの検索ボタンは、SWELLがページを読み込んだときにクリックの処理を付けています。コピーしたボタンにはその処理が付いていないので、元のボタンをクリックしたことにして動かしますobserver.disconnect():コピーは1回だけなので、見張りをやめますpc.addEventListener('change', copy):画面幅が変わったときにも、コピーを試します。スマホの幅でスクロールしてから画面を広げると、data-scrolledは"true"のまま変わらないので、これがないとグロナビが空のままになります
合計は 1,125→957(−168) になりました。PCでスクロールすると、追従ヘッダーにグロナビ(169要素)がコピーされ、検索ボタンも開くことを確かめています。
方法Bは、読み込んだ直後の要素を減らすもので、PCでスクロールした後は要素が元の数に戻ります。スマホでは最後までコピーしないので、効果がそのまま残ります。
対策3:グロナビ自体を整理する
対策1と2で、グロナビの出力は1か所になります。そのうえで、ほとんど使われていない項目があれば、メニューから外します。
当サイトの「そのほかの記事」には、子項目が10件ありました。これを外すと、合計は 957→924(−33) になりました。
対策1と2をしていない状態で同じ10件を外すと、1,232→1,144(−88) でした。メニューが3か所に出力されているので、1項目減らすと3か所分減ります。メニューの説明文(項目の下に出る小さな文字)を入れた項目は、ヘッダーのグロナビと追従ヘッダーで、要素が1つずつ増えます。
実測:対策前後の比較

| 段階 | 合計要素数 | 減った数 |
|---|---|---|
| 対策前 | 1,232 | – |
| 対策1:スマホ用の短いメニュー | 1,125 | −107 |
| 対策2(方法B):追従ヘッダーのグロナビを後からコピー | 957 | −168 |
| 対策3:グロナビの子項目を10件外す | 924 | −33 |
| 合計 | −308(約25%減) |
(対策2を方法Aにした場合は、対策2の後が949になります。)
時間はどれくらい変わったか
PSIのモバイルの計測と同じように、CPUの速さを4分の1に落として、ページを読み込むときのスタイルの計算時間を比べました(5回の中央値)。
| 対策前(1,144) | 対策後(924) | |
|---|---|---|
| スタイルの計算(PC) | 約128ms | 約111ms |
| スタイルの計算(スマホ) | 約101ms | 約98ms |
PCで1割ほど短くなりましたが、スマホではほとんど変わりませんでした。レイアウトの計算時間は、計測ごとのばらつきのほうが大きく、差がわかりませんでした。
要素を約2割減らしても、体感でわかるほど速くなるわけではありません。この対策の目的は、読者に見えないまま出力されている部品をなくして、ページを軽くしておくことです。
減らさなくてよいもの
要素の数だけを見ると、本文や目次、関連記事を削りたくなります。しかし、どれも読者が読むための部分です。PSIの数字を下げるために、記事の内容や読みやすさを削る必要はありません。
減らす対象は、今回のメニューのように、同じものが重複している部分や、表示されないまま出力されている部分に絞るのがおすすめです。
注意点
- キャッシュを削除してから確かめる:SWELLのキャッシュやキャッシュ系のプラグインを使っている場合は、メニューや設定を変えたら削除してください
- 方法Bは、SWELLの更新後に見直す:子テーマの
fix_header.phpは、SWELL本体のファイルの代わりに使われます。SWELLの更新で本体のfix_header.phpが変わった場合は、子テーマのファイルにも同じ変更を反映してください - 方法Bの表示を確かめる:PCでスクロールして、追従ヘッダーにグロナビが出ること、サブメニューと検索ボタンが動くことを確かめてください
よくある質問・エラー対処
Q. PSIの「DOM の深さ」の最も深いところが、Googleのサイト内検索になっています
→ Googleのサイト内検索(プログラム可能検索エンジン)のウィジェットは、ページを読み込んだ後にJavaScriptで検索窓の部品を追加します。当サイトでも、最も深いところはその部品でした。テーマや記事の作りとは関係なく、Googleが用意している部品なので、気にしなくて大丈夫です。
Q. 要素数が800くらいでも、対策したほうがいいですか
→ 急いで対策する必要はありません。ただ、この記事のようにメニューが重複している場合は、スマホメニュー用の短いメニューを作る(対策1)だけで、手間をかけずに100要素ほど減らせます。
Q. 方法Bで、追従ヘッダーにグロナビが表示されません
→ 子テーマのファイルの場所が swell_child/parts/header/fix_header.php になっているか確認してください。あわせて、カスタマイザーの「ヘッダーを追従させる(PC)」がオンになっているか、画面幅が960px以上かも確認してください。
おわりに(まとめ)
SWELLでは、本文を削らずに、メニューの重複を減らすだけで要素数を約25%減らせます。
- ✅ PSIの「DOM サイズを最適化する」はスコアなしの項目。数字よりも、どこが要素を増やしているかを見る
- ✅ 検証ツールのConsoleで、場所ごとの要素数を数えられる。隠した要素も数に入る
- ✅ SWELLでは、グロナビがヘッダー・追従ヘッダー・スマホメニューの3か所に出力されることがある
- ✅ スマホ用の短いメニューを「スマホ開閉メニュー内」に割り当て、追従ヘッダーのグロナビを減らす
- ✅ 要素数は1,232→924になったが、時間の短縮は小さい。読者のための本文や目次は削らない
この記事が、少しでも誰かのお役に立てれば幸いです。
関連記事
- 【WordPress高速化】SWELLのPrefetchは不要:SWELLのPrefetchが移動時に使われない理由と、WordPress本体の先読みに一本化する方法(シリーズ第9回)
- 【WordPress高速化】X・Instagramの遅延読み込み:Intersection Observerで、XとInstagramの埋め込みを投稿に近づくまで読み込まない方法(シリーズ第8回)
- 【WordPress高速化】HCBを必要な記事だけで読み込む:Highlighting Code BlockのCSSとJSを、コードブロックがある記事だけで読み込む方法(シリーズ第7回)
- 【WordPress高速化】h3まで入れた目次の高さを自動設定:h2とh3を分けて数え、目次の高さを記事ごとに自動で確保する方法(シリーズ第6回)
- 【WordPress高速化】目次の高さを見出しの数で自動設定:PHPで見出しを数えてCSS変数に渡し、目次の高さを記事ごとに自動で確保する方法(シリーズ第5回)
- 【WordPress高速化】AdSenseを枠ごとに遅延読み込み:Intersection Observerで、画面に近づいた広告枠だけを読み込む方法(シリーズ第4回)
- 【WordPress高速化】YouTube埋め込みを遅延読み込み:Intersection Observerの基本と、画面外の動画の読み込みを0KBにした方法(シリーズ第3回)
- 【WordPress高速化】SWELLのCLSをCSSで改善:広告枠などに高さを確保して、CLSを0.14から0.0008に下げた実例(シリーズ第2回)
- 【WordPress高速化】CLSの原因を検証ツールで探す方法:検証ツールで、ずれた場所を見つけて高さを測る方法(シリーズ第1回)
※1回買えば複数サイトで使い回しOK
当サイトの記事で使用したVBAなどのサンプルをDLできます
この記事のサンプルはありません!
ダウンロードページへは下のカードをクリックすればジャンプできます。
よろしければご利用ください!


