2026年8月13日

2026年8月13日

WordPressでResource Timing API(Performance API)を使いリソースの読み込み時間を計測する方法

ブラウザのPerformance APIに含まれるResource Timingを使うと、WordPressのページが読み込むCSS・JavaScript・画像・フォントの一つひとつについて、DNS解決、接続、サーバーの応答待ち、ダウンロードに何ミリ秒かかったかを数値で取り出せます。この記事では、管理者だけに計測スクリプトを読み込ませる子テーマのコード、結果を表にまとめるJavaScript、数値の読み方、外部ドメインのリソースで値が0になる理由とその対処までを順に説明します。どのプラグインのファイルが遅いのか、CDNが効いているのかを自分のサイトで確かめたい方向けの内容です。

Resource Timingで分かること

ページ上の各リソースは、ブラウザ内で PerformanceResourceTiming という記録として残ります。主な項目と意味は次のとおりです。

  • name: リソースのURL
  • initiatorType: 読み込みのきっかけ(link、script、img、css、fetch など)
  • duration: 開始から受信完了までの合計時間(ミリ秒)
  • domainLookupStart / domainLookupEnd: DNS解決の開始と終了
  • connectStart / connectEnd: TCP接続(HTTPSならTLSを含む)の開始と終了
  • requestStart / responseStart: リクエスト送信から最初の1バイトが届くまで(サーバー側の処理時間の目安)
  • responseEnd: 最後のバイトを受け取った時刻
  • transferSize / encodedBodySize / decodedBodySize: 転送量、圧縮後の本体サイズ、展開後のサイズ

ここで大事なのは、合計時間の内訳が分かる点です。同じ「遅いファイル」でも、応答待ちが長いならサーバーやPHPの処理、ダウンロードが長いならファイルサイズや回線、接続が長いなら新しいドメインへの接続の多さ、と打ち手が変わります。PageSpeed Insightsのような診断ツールは結果を要約して示しますが、Resource Timingは自分の端末・自分の回線での生の数値を確認できるのが利点です。

なお、renderBlockingStatus(描画をブロックしたかどうか)や responseStatus(HTTPステータス)のように比較的新しい項目もありますが、対応ブラウザが限られます(2026年10月現在)。使う場合は値が undefined でも動くように書いてください。

子テーマで計測スクリプトを管理者だけに読み込む

計測用のスクリプトは訪問者に配る必要がないので、ログイン中の管理者が URL に ?rt_debug=1 を付けたときだけ読み込むようにします。作業は子テーマの functions.php と、子テーマ内に作る js/resource-timing.js で行います。編集の前にはバックアップを取り、可能ならステージング環境で試してください。

<?php
// 子テーマの functions.php
add_action( 'wp_enqueue_scripts', function () {
	// 管理者が ?rt_debug=1 を付けて開いたときだけ読み込む
	if ( ! current_user_can( 'manage_options' ) || ! isset( $_GET['rt_debug'] ) ) {
		return;
	}
	$path = get_stylesheet_directory() . '/js/resource-timing.js';
	if ( ! file_exists( $path ) ) {
		return;
	}
	wp_enqueue_script(
		'my-resource-timing',
		get_stylesheet_directory_uri() . '/js/resource-timing.js',
		array(),
		filemtime( $path ),
		array(
			'strategy'  => 'defer',
			'in_footer' => true,
		)
	);
} );

wp_enqueue_script() の第5引数に配列を渡して strategy を指定する書き方は WordPress 6.3 以降で使えます。計測スクリプト自体がページの表示を遅らせては本末転倒なので、defer で読み込みます。後から読み込まれても、次に説明する buffered: true によってそれまでの記録をさかのぼって受け取れます。

$_GET['rt_debug'] は有無を見るだけで値は使っていないため、ここでのサニタイズは不要です。値を画面に出したり保存したりする場合は、sanitize_text_field() などで処理してください。

計測結果を表にまとめるJavaScript

performance.getEntriesByType('resource') でも一覧は取れますが、PerformanceObserver を使うと、ページ表示後に遅れて読み込まれた画像や非同期の通信も漏らさず拾えます。

// 子テーマの js/resource-timing.js
( function () {
	if ( ! ( 'PerformanceObserver' in window ) ) {
		return;
	}
	const entries = [];
	const observer = new PerformanceObserver( ( list ) => {
		entries.push( ...list.getEntries() );
	} );
	// buffered: true で、このスクリプトより前に記録された分も受け取る
	observer.observe( { type: 'resource', buffered: true } );

	const ms = ( v ) => Math.round( v );

	function toRow( e ) {
		// 外部ドメインで Timing-Allow-Origin が無いと内訳は 0 になる
		const detailed = e.responseStart > 0;
		return {
			file: e.name.split( '?' )[ 0 ].slice( -50 ),
			type: e.initiatorType,
			total: ms( e.duration ),
			dns: detailed ? ms( e.domainLookupEnd - e.domainLookupStart ) : '-',
			connect: detailed ? ms( e.connectEnd - e.connectStart ) : '-',
			wait: detailed ? ms( e.responseStart - e.requestStart ) : '-',
			download: detailed ? ms( e.responseEnd - e.responseStart ) : '-',
			kb: ms( e.transferSize / 1024 ),
			cache: e.transferSize === 0 && e.decodedBodySize > 0 ? 'cache' : '',
			blocking: e.renderBlockingStatus || '',
		};
	}

	function report() {
		const rows = entries.map( toRow ).sort( ( a, b ) => b.total - a.total );
		console.log( 'リソース数:', rows.length );
		console.table( rows.slice( 0, 15 ) );

		// ドメインごとの件数・転送量・最大時間
		const hosts = {};
		entries.forEach( ( e ) => {
			const host = new URL( e.name ).host;
			hosts[ host ] = hosts[ host ] || { count: 0, kb: 0, max: 0 };
			hosts[ host ].count++;
			hosts[ host ].kb += e.transferSize / 1024;
			hosts[ host ].max = Math.max( hosts[ host ].max, e.duration );
		} );
		Object.values( hosts ).forEach( ( h ) => {
			h.kb = ms( h.kb );
			h.max = ms( h.max );
		} );
		console.table( hosts );
	}

	// 遅延読み込みの画像なども拾えるよう、load の少し後に出力する
	window.addEventListener( 'load', () => setTimeout( report, 3000 ) );
} )();

管理者でログインした状態で https://example.com/?rt_debug=1 のように開き、開発者ツールのコンソールを見ると、時間のかかった順に上位15件と、ドメイン別の集計が表示されます。ログイン中はページキャッシュが効かない設定のサイトが多いため、HTMLそのものの速さは訪問者の体験と違う点に注意してください。CSSや画像などの静的ファイルの比較には十分使えます。

数値の読み方と、遅い原因の見分け方

表を見るときは、合計時間(total)よりも内訳の列に注目します。

  • wait(応答待ち)が長い: サーバーが最初の1バイトを返すまでに時間がかかっています。PHPで動的に生成されるファイル(プラグインが admin-ajax.php やREST API経由で返すCSSなど)や、キャッシュされていない画像変換が疑わしいです。静的ファイルなのに長い場合は、サーバーの負荷やCDNのキャッシュミスを確認します。
  • download(受信)が長い: ファイルが大きいか、回線が遅い状態です。kb列と合わせて見て、画像の圧縮やWebP/AVIFへの変換、不要なJavaScriptの削除を検討します。
  • connect・dns が毎回出る: 外部ドメイン(Googleフォント、広告、解析タグなど)ごとに新しい接続が発生しています。ドメイン別の表で件数の少ないドメインが多い場合は、使っていないサービスを減らすか、重要なものに preconnect を検討します。
  • cache 列に表示がある: transferSize が0で中身はある、つまりブラウザのキャッシュから読まれたと考えられる行です。再読み込みしても表示されないファイルは、キャッシュ用のHTTPヘッダーが効いていない可能性があります。なお304応答で検証しただけの場合はヘッダー分の小さな転送量が記録されるので、0にはなりません。
  • blocking 列が blocking: 描画を止めたリソースです。対応ブラウザでのみ値が入ります。CSSやヘッダー内のJavaScriptで多く出る場合は、defer の付与や読み込み場所の見直しが効果的です。

計測値は回線状況やブラウザの拡張機能で揺れるので、シークレットウィンドウで数回測り、傾向で判断するのが安全です。開発者ツールのネットワーク速度の制限機能を使うと、モバイル回線に近い条件での差も見られます。

外部ドメインのリソースで内訳が0になる理由

Googleフォントや外部CDN、画像配信用のサブドメインなど、ページと別のオリジンから読み込んだリソースは、相手のサーバーが Timing-Allow-Origin ヘッダーを返さない限り、duration などごく一部を除いて内訳の値が0になります。サイズの項目も0になるため、上のスクリプトでは内訳を「-」と表示しています。これは別サイトへのアクセス状況を他のページから覗き見られないようにするための仕様で、不具合ではありません。

自分で管理している静的ファイル用のサブドメインやCDNであれば、そちらの応答にヘッダーを付けると詳しい値が取れます。Apacheで配信している場合の例です(mod_headers が有効である前提です)。

<IfModule mod_headers.c>
	<FilesMatch "\.(css|js|woff2|png|jpe?g|webp|avif|svg)$">
		Header set Timing-Allow-Origin "https://example.com"
	</FilesMatch>
</IfModule>

値には計測するページ側のオリジンを指定します。* も書けますが、許可範囲を必要以上に広げないためにも、自サイトのオリジンに絞るのがおすすめです。CDNの管理画面でレスポンスヘッダーを追加できるサービスもあるので、利用中のCDNのドキュメントを確認してください。

記録の上限と、訪問者のデータを集めたい場合

ブラウザが保持するResource Timingの記録には上限があり、仕様では最低250件とされています。画像の多いページや無限スクロールでは上限を超えることがあり、performance.getEntriesByType() だけに頼ると後半が欠けます。PerformanceObserver で受け取る方法なら、この上限に関係なく新しい記録を拾えます。上限そのものは performance.setResourceTimingBufferSize() で変更できます。

実際の訪問者の環境での数値を集めたい場合は、この仕組みを navigator.sendBeacon() で自前のREST APIに送る方法もありますが、誰でも送信できる受け口はいたずらの送信先になりやすく、件数制限や値の検証が欠かせません。また、URLや端末の情報は個人に関わるデータになりうるため、収集する場合はプライバシーポリシーへの記載も必要です。まずはPageSpeed InsightsやSearch ConsoleのCore Web Vitalsレポートで示される実ユーザーデータ(Chromeユーザーエクスペリエンスレポート由来)を確認し、それでも原因が絞れないときに自前の収集を検討する、という順番が手間とリスクの両面で無理がありません。

まとめ

  • Resource Timingを使うと、各リソースの読み込み時間をDNS・接続・応答待ち・受信に分けて確認できます。
  • 計測スクリプトは子テーマから管理者だけに読み込ませ、strategy => 'defer' で表示への影響を避けます。
  • PerformanceObserver に buffered: true を付けると、先に記録された分や遅れて読み込まれた分も漏れなく受け取れます。
  • 応答待ちが長いならサーバー側、受信が長いならファイルサイズ、と内訳で打ち手を分けて考えます。
  • 外部ドメインのリソースは Timing-Allow-Origin が無いと内訳が0になります。自分で管理する配信元ならヘッダーで許可できます。

お気軽にご相談ください

お見積りへ お問い合わせへ
▲