wp-performance-review
WordPressのパフォーマンスコードレビューと最適化分析を実施します。WordPressのPHPコードのパフォーマンス問題のレビュー、テーマ・プラグインのスケーラビリティ監査、WP_Queryの最適化、キャッシング戦略の分析、本番運用前のコード確認、アンチパターンの検出に対応します。ユーザーが「パフォーマンスレビュー」「最適化監査」「WordPressが遅い」「クエリが遅い」「高トラフィック」「WordPressのスケーリング」「コードレビュー」「タイムアウト」「500エラー」「メモリ不足」「サイトが読み込めない」といった内容を提示した場合にも適用されます。データベースクエリ、フック、オブジェクトキャッシュ、AJAX、テンプレート読み込み、エディタ側のパフォーマンスにおけるアンチパターンを検出します。
description の原文を見る
WordPress performance code review and optimization analysis. Use when reviewing WordPress PHP code for performance issues, auditing themes/plugins for scalability, optimizing WP_Query, analyzing caching strategies, checking code before launch, or detecting anti-patterns, or when user mentions "performance review", "optimization audit", "slow WordPress", "slow queries", "high-traffic", "scale WordPress", "code review", "timeout", "500 error", "out of memory", or "site won't load". Detects anti-patterns in database queries, hooks, object caching, AJAX, template loading, and editor-side performance.
SKILL.md 本文
WordPress Performance Review スキル
概要
WordPress テーマ、プラグイン、カスタムコードの体系的なパフォーマンスコードレビュー。コアプリンシパル: 重大な問題(OOM、制限なしのクエリ、キャッシュ回避)を最初にスキャンし、その後警告、最後に最適化をレポートします。行番号と重要度レベルと共に報告してください。
使用場面
以下の場合に使用してください:
- WordPress テーマまたはプラグインの PR/コードをレビューする
- ユーザーがページロードの遅延、タイムアウト、または 500 エラーを報告している
- 高トラフィックイベント(ローンチ、セール、バイラル)の前に監査する
- WP_Query またはデータベース操作を最適化する
- メモリ枯渇または DB ロックを調査する
以下の場合は使用しないでください:
- セキュリティのみの監査(wp-security-review が利用可能な場合はそちらを使用)
- Gutenberg ブロック開発パターン(wp-block-development が利用可能な場合はそちらを使用)
- WordPress 特有でない一般的な PHP コードレビュー
- WordPress パフォーマンス動作に関係しない製品または UX レビュー
コードレビューワークフロー
- ファイルタイプを特定し、以下の関連チェックを適用します
- 重大なパターンを最初にスキャン(OOM、制限なしのクエリ、キャッシュ回避)
- 警告をチェック(効率は悪いが致命的ではない)
- 最適化に注記(あると良い改善)
- 行番号付きでレポート、下記の出力形式を使用します
ファイルタイプ別チェック
プラグイン/テーマ PHP ファイル(functions.php、plugin.php、*.php)
以下をスキャンしてください:
query_posts()→ 重大: 使用禁止 - メインクエリを破損posts_per_page.*-1またはnumberposts.*-1→ 重大: 制限なしのクエリsession_start()→ 重大: ページキャッシュを回避add_action.*init.*またはadd_action.*wp_loaded→ 高コスト処理が毎リクエスト実行されないか確認update_optionまたはadd_option(非管理者コンテキスト) → 警告: ページロード時の DB 書き込みwp_remote_getまたはwp_remote_post(キャッシュなし) → 警告: ブロッキング HTTP
WP_Query / データベースコード
以下をスキャンしてください:
posts_per_page引数の欠落 → 警告: ブログ設定にデフォルト'meta_query'で'value'比較使用 → 警告: インデックスなしの列スキャン- 大規模な
post__not_in配列(ページネーション/ソート付き) → 警告: 高コスト SQL を生成する可能性; ケースバイケースで確認 LIKE '%term%'(先頭のワイルドカード) → 警告: 全テーブルスキャンno_found_rows => trueの欠落(ページネーションなし) → 情報: 不要なカウント
AJAX ハンドラ(wp_ajax_*、REST エンドポイント)
以下をスキャンしてください:
admin-ajax.phpの使用 → 情報: REST API を検討- POST メソッド(読み取り操作用) → 警告: キャッシュを回避
setIntervalまたはポーリングパターン → 重大: Self-DDoS リスク- nonce 検証の欠落 → セキュリティ問題(パフォーマンスではないが、フラグを立てる)
テンプレートファイル(テーマの *.php)
以下をスキャンしてください:
- カスタムクエリ、キャッシュなしの
get_post_meta()、またはループ内のリモート呼び出し → 警告: 繰り返し処理/N+1 リスク - ループ内のデータベースクエリ(N+1) → 重大: クエリ乗算
- テンプレート内の
wp_remote_get→ 警告: レンダリングをブロック
JavaScript ファイル
以下をスキャンしてください:
- 読み取り操作用の
$.post(→ 警告: キャッシュ可能性のため GET を使用 setInterval.*fetch\|ajax→ 重大: ポーリングパターンimport _ from 'lodash'→ 警告: フルライブラリインポートでバンドルを肥大化- ロード時に AJAX 呼び出しをする インライン
<script>→ 必要性を確認
ブロックエディタ/ Gutenberg ファイル(block.json、ブロック内の *.js)
以下をスキャンしてください:
- エディタ側の重いデータ取得またはプレビュー ロジック → 警告: エディタロードを遅延
- render コールバック内の
wp_kses_post($content)→ 警告: InnerBlocks を破損 - 大規模なエディタバンドルまたは広範インポート → 警告: エディタランタイムを肥大化
アセット登録(functions.php、*.php)
以下をスキャンしてください:
wp_enqueue_script(version なし) → 情報: キャッシュバスティング問題wp_enqueue_script(defer/async戦略なし) → 情報: レンダリングをブロックTHEME_VERSION定数の欠落 → 情報: バージョン管理wp_enqueue_script(条件チェックなし) → 警告: 特定ページのみ必要な場合、グローバルでロード
トランジェント & オプション
以下をスキャンしてください:
- 動的キーを持つ
set_transient(例:user_{$id}) → 警告: オブジェクトキャッシュなしのテーブル肥大化 - 頻繁に変更されるデータの
set_transient→ 警告: キャッシング目的を無効化 - トランジェント内の大規模データ(共有ホスティング) → 警告: オブジェクトキャッシュなしの DB 肥大化
WP-Cron
以下をスキャンしてください:
DISABLE_WP_CRON定数の欠落 → 情報: Cron がページリクエスト時に実行- 長時間実行される Cron コールバック(全ユーザー/投稿をループ) → 重大: Cron キューをブロック
- すでにスケジュール済みか確認なしの
wp_schedule_event→ 警告: 重複スケジュール
迅速な検出用検索パターン
# 重大な問題 - 最初にこれをスキャン
rg -n "posts_per_page\s*.*-1|numberposts\s*.*-1" .
rg -n "query_posts\s*\(" .
rg -n "session_start\s*\(" .
rg -n "setInterval.*(fetch|ajax|\\$\\.)" .
# フロントエンドでのデータベース書き込み
rg -n "update_option|add_option" . -g '*.php'
# キャッシュなしの高コスト関数
rg -n "url_to_postid|attachment_url_to_postid|count_user_posts" .
# キャッシュなしの外部 HTTP
rg -n "wp_remote_get|wp_remote_post|file_get_contents\s*\(\s*['\"]https?://" .
# キャッシュ回避リスク
rg -n "setcookie|session_start" .
# PHP コード アンチパターン
rg -n "in_array\s*\(" . -g '*.php' # 厳密比較を手動確認
rg -n "<<<" .
rg -n "cache_results\s*=>\s*false|cache_results\s*,\s*false" .
# JavaScript バンドル問題
rg -n "import\s+_\s+from\s+['\"]lodash['\"]" . -g '*.{js,jsx,ts,tsx}'
# アセットロード問題
rg -n "wp_enqueue_script|wp_enqueue_style" . -g '*.php'
# トランジェント誤用
rg -n "set_transient\s*\([^,]+\\$" . -g '*.php'
rg -n "set_transient" . -g '*.php' # 前の get_transient() チェックを手動確認
# WP-Cron 問題
rg -n "wp_schedule_event" . -g '*.php' # wp_next_scheduled() ガードを手動確認
プラットフォームコンテキスト
異なるホスティング環境では異なるアプローチが必要です:
マネージド WordPress ホスト(WP Engine、Pantheon、Pressable、WordPress VIP など):
- 多くの場合、オブジェクトキャッシュがすぐに利用可能
- プラットフォーム固有のヘルパー関数がある場合があります(例: VIP の
wpcom_vip_*) - ホストドキュメントで推奨パターンを確認してください
セルフホスト / 標準ホスティング:
- 高コスト関数用のオブジェクトキャッシュラッパーを手動で実装
- 永続的オブジェクトキャッシュ用の Redis または Memcached プラグインを検討
- キャッシングレイヤー設定に対する責任が増加
共有ホスティング:
- 制限なしのクエリと外部 HTTP について特に注意
- リソースが限定されるため、パフォーマンス問題がより早く表面化
- 永続的オブジェクトキャッシュがない場合があります
クイックリファレンス: 重大なアンチパターン
データベースクエリ
// ❌ CRITICAL: 制限なしのクエリ。
'posts_per_page' => -1
// ✅ GOOD: 合理的な制限を設定し、必要に応じてページネーション。
'posts_per_page' => 100,
'no_found_rows' => true, // ページネーションなしの場合カウントをスキップ。
// ❌ CRITICAL: query_posts() を使用しないでください。
query_posts( 'cat=1' ); // ページネーション、条件を破損。
// ✅ GOOD: WP_Query または pre_get_posts フィルタを使用。
$query = new WP_Query( array( 'cat' => 1 ) );
// またはメインクエリを変更:
add_action( 'pre_get_posts', function( $query ) {
if ( $query->is_main_query() && ! is_admin() ) {
$query->set( 'cat', 1 );
}
} );
// ❌ WARNING: 検証されていない ID はロジックバグを隠し、不要なクエリをトリガー。
$query = new WP_Query( array( 'p' => intval( $maybe_false_id ) ) );
// ✅ GOOD: クエリ前に ID を検証。
$post_id = absint( $maybe_false_id );
if ( $post_id > 0 ) {
$query = new WP_Query( array( 'p' => $post_id ) );
}
// ❌ WARNING: 先頭ワイルドカード付き LIKE (全テーブルスキャン)。
$wpdb->get_results( "SELECT * FROM wp_posts WHERE post_title LIKE '%term%'" );
// ✅ GOOD: 末尾ワイルドカードのみを使用、または WP_Query の 's' パラメータを使用。
$wpdb->get_results( $wpdb->prepare(
"SELECT * FROM wp_posts WHERE post_title LIKE %s",
$wpdb->esc_like( $term ) . '%'
) );
// ❌ WARNING: 大規模な NOT IN クエリは高コストになる可能性、特にページネーション付き。
'post__not_in' => $excluded_ids
// ✅ GOOD: ポジティブインクルージョン、小規模除外リスト、または事前計算した候補 ID を優先。
'post__in' => $candidate_ids
フック & アクション
// ❌ WARNING: init を経由して毎リクエスト実行されるコード。
add_action( 'init', 'expensive_function' );
// ✅ GOOD: 高コスト処理を実行前にコンテキストをチェック。
add_action( 'init', function() {
if ( is_admin() || wp_doing_cron() ) {
return;
}
// フロントエンドのみのコードはここ。
} );
// ❌ CRITICAL: 毎ページロード時のデータベース書き込み。
add_action( 'wp_head', 'prefix_bad_tracking' );
function prefix_bad_tracking() {
update_option( 'last_visit', time() );
}
// ✅ GOOD: オブジェクトキャッシュバッファを使用し、cron 経由でフラッシュ。
add_action( 'shutdown', function() {
wp_cache_incr( 'page_views_buffer', 1, 'counters' );
} );
// ❌ WARNING: admin-ajax.php を使用してしまった。
// 優先: register_rest_route() - よりスリムなブートストラップ。
PHP コード
// ❌ WARNING: O(n) ルックアップ - 連想配列で isset() を使用。
in_array( $value, $array ); // また strict = true も欠落。
// ✅ GOOD: isset() での O(1) ルックアップ。
$allowed = array( 'foo' => true, 'bar' => true );
if ( isset( $allowed[ $value ] ) ) {
// 処理。
}
// ❌ WARNING: Heredoc は後のエスケープを防止。
$html = <<<HTML
<div>$unescaped_content</div>
HTML;
// ✅ GOOD: 出力時にエスケープ。
printf( '<div>%s</div>', esc_html( $content ) );
キャッシング問題
// ❌ WARNING: キャッシュなしの高コスト関数呼び出し。
url_to_postid( $url );
attachment_url_to_postid( $attachment_url );
count_user_posts( $user_id );
wp_oembed_get( $url );
// ✅ GOOD: オブジェクトキャッシュでラップ(どのホストでも機能)。
function prefix_cached_url_to_postid( $url ) {
$cache_key = 'url_to_postid_' . md5( $url );
$post_id = wp_cache_get( $cache_key, 'url_lookups' );
if ( false === $post_id ) {
$post_id = url_to_postid( $url );
wp_cache_set( $cache_key, $post_id, 'url_lookups', HOUR_IN_SECONDS );
}
return $post_id;
}
// ✅ GOOD: WordPress VIP 上では代わりにプラットフォームヘルパーを使用。
// wpcom_vip_url_to_postid(), wpcom_vip_attachment_url_to_postid() など。
// ❌ WARNING: 大規模な自動ロードオプション。
add_option( 'prefix_large_data', $data ); // 追加: , '', 'no' for autoload。
// ❌ INFO: バッチルックアップ用の wp_cache_get_multiple が欠落。
foreach ( $ids as $id ) {
wp_cache_get( "key_{$id}" );
}
AJAX & 外部リクエスト
// ❌ WARNING: AJAX POST リクエスト(キャッシュを回避)。
$.post( ajaxurl, data ); // 優先: 読み取り操作用の $.get()。
// ❌ CRITICAL: ポーリングパターン(Self-DDoS)。
setInterval( () => fetch( '/wp-json/...' ), 5000 );
// ❌ WARNING: ページロード時の同期外部 HTTP。
wp_remote_get( $url ); // 結果をキャッシュまたは cron に移動。
// ✅ GOOD: タイムアウトを設定しエラーを処理。
$response = wp_remote_get( $url, array( 'timeout' => 2 ) );
if ( is_wp_error( $response ) ) {
return get_fallback_data();
}
WP Cron
// INFO: 高トラフィックまたは cron が多いサイトではリクエスト駆動の cron で十分でない場合があります。
// wp-config.php に追加を検討:
define( 'DISABLE_WP_CRON', true );
// サーバー cron 経由で実行: * * * * * wp cron event run --due-now
// ❌ CRITICAL: 長時間実行 cron が全キューをブロック。
add_action( 'my_daily_sync', function() {
foreach ( get_users() as $user ) { // 50k ユーザー = 数時間。
sync_user_data( $user );
}
} );
// ✅ GOOD: 再スケジュールでのバッチ処理。
add_action( 'my_batch_sync', function() {
$offset = (int) get_option( 'sync_offset', 0 );
$users = get_users( array( 'number' => 100, 'offset' => $offset ) );
if ( empty( $users ) ) {
delete_option( 'sync_offset' );
return;
}
foreach ( $users as $user ) {
sync_user_data( $user );
}
update_option( 'sync_offset', $offset + 100 );
wp_schedule_single_event( time() + 60, 'my_batch_sync' );
} );
// ❌ WARNING: スケジュール済みか確認せずスケジュール。
wp_schedule_event( time(), 'hourly', 'my_task' ); // 重複を作成!
// ✅ GOOD: スケジュール前に確認。
if ( ! wp_next_scheduled( 'my_task' ) ) {
wp_schedule_event( time(), 'hourly', 'my_task' );
}
キャッシュ回避問題
// ❌ CRITICAL: プラグインがフロントエンドで PHP セッションを開始(全ページキャッシュを回避)。
session_start(); // プラグインでこれを確認 - サイト全体がキャッシュ不可になります!
// ❌ WARNING: ユニークなクエリパラメータがキャッシュミスを作成。
// https://example.com/?utm_source=fb&utm_campaign=123&fbclid=abc
// ユニークな URL ごと = 個別キャッシュエントリ = キャッシュミス。
// ソリューション: CDN/エッジレベルでマーケティングパラメータをストリップ。
// ❌ WARNING: パブリックページでクッキーを設定。
setcookie( 'visitor_id', $id ); // そのユーザーのキャッシュを防止。
トランジェント誤用
// ❌ WARNING: 動的トランジェントキーがテーブル肥大化を作成(オブジェクトキャッシュなし)。
set_transient( "user_{$user_id}_cart", $data, HOUR_IN_SECONDS );
// 10,000 ユーザー = wp_options に 10,000 行!
// ✅ GOOD: ユーザー固有データ用にオブジェクトキャッシュを使用。
wp_cache_set( "cart_{$user_id}", $data, 'user_carts', HOUR_IN_SECONDS );
// ❌ WARNING: 頻繁に変更されるデータのトランジェント(目的を無効化)。
set_transient( 'visitor_count', $count, 60 ); // 毎分変更。
// ✅ GOOD: 不安定なデータ用にオブジェクトキャッシュを使用。
wp_cache_set( 'visitor_count', $count, 'stats' );
// ❌ WARNING: 共有ホスティングでのトランジェント内の大規模データ。
set_transient( 'api_response', $megabytes_of_json, DAY_IN_SECONDS );
// オブジェクトキャッシュなし = wp_options 内のシリアライズされた blob。
// ✅ GOOD: トランジェント使用前にホスティングをチェック。
if ( wp_using_ext_object_cache() ) {
set_transient( 'api_response', $data, DAY_IN_SECONDS );
} else {
// 共有ホスティングではファイル保存またはキャッシュをスキップ。
}
アセットロード
// ❌ WARNING: 特定ページのみ必要な場合、アセットがグローバルでロード。
add_action( 'wp_enqueue_scripts', function() {
wp_enqueue_script( 'contact-form-js', ... );
wp_enqueue_style( 'contact-form-css', ... );
} );
// ✅ GOOD: ページ/テンプレートベースの条件付きエンキュー。
add_action( 'wp_enqueue_scripts', function() {
if ( is_page( 'contact' ) || is_page_template( 'contact-template.php' ) ) {
wp_enqueue_script( 'contact-form-js', ... );
wp_enqueue_style( 'contact-form-css', ... );
}
} );
// ✅ GOOD: ショップページでのみ WooCommerce アセットをロード。
add_action( 'wp_enqueue_scripts', function() {
if ( ! is_woocommerce() && ! is_cart() && ! is_checkout() ) {
wp_dequeue_style( 'woocommerce-general' );
wp_dequeue_script( 'wc-cart-fragments' );
}
} );
外部 API リクエスト
// ❌ WARNING: タイムアウトが設定されていない(デフォルト 5 秒)。
wp_remote_get( $url ); // タイムアウト設定: array( 'timeout' => 2 )。
// ❌ WARNING: API 失敗のエラー処理がない。
$response = wp_remote_get( $url );
echo $response['body']; // 最初に is_wp_error() をチェック!
サイトマップ & リダイレクト
// ❌ WARNING: ディープアーカイブ用のサイトマップ生成(クローラーが大量にアクセス)。
// ソリューション: 古い投稿タイプを除外、生成済みサイトマップをキャッシュ。
// ❌ CRITICAL: CPU を消費するリダイレクトループ。
// デバッグ: x-redirect-by ヘッダー、wp_debug_backtrace_summary()。
投稿メタクエリ
// ❌ WARNING: インデックスなしの meta_value を検索。
'meta_query' => array(
array(
'key' => 'color',
'value' => 'red',
),
)
// より良い: タクソノミーを使用またはメタキー名に値をエンコード。
// ❌ WARNING: バイナリメタ値がバリュースキャンを必要。
'meta_key' => 'featured',
'meta_value' => 'true',
// より良い: 'is_featured' キーの有無 = true/false。
パターン詳細なコンテキスト用: references/anti-patterns.md をロード
重要度定義
| 重要度 | 説明 |
|---|---|
| 重大 | スケール時に障害を引き起こす(OOM、500 エラー、DB ロック) |
| 警告 | 負荷下でパフォーマンスを低下 |
| 情報 | 最適化の機会 |
出力形式
以下のようにして成果をまとめてください:
## Performance Review: [ファイル名/コンポーネント]
### 重大な問題
- **Line X**: [問題] - [説明] - [修正]
### 警告
- **Line X**: [問題] - [説明] - [修正]
### 推奨事項
- [最適化の機会]
### サマリー
- 合計問題数: X 重大、Y 警告、Z 情報
- 推定影響: [高/中/低]
一般的なミス
パフォーマンスレビューを実施する際、これらのエラーを避けてください:
| ミス | なぜ間違いか | 修正 |
|---|---|---|
管理者のみのコード内の posts_per_page => -1 をフラグする | 管理者クエリはパブリックスケールに直面しない | コンテキストを確認 - 管理者、CLI、cron はより低リスク |
プラグイン内の session_start() を見落とす | キャッシュ回避がサイト全体に影響 | すべてのコードで常に session_start をグレップ |
ページネーション非使用クエリの no_found_rows を無視 | 小さい最適化だが積み重なる | 警告ではなく情報としてフラグ |
| 共有ホスティングでのオブジェクトキャッシュを推奨 | 多くの共有ホストは永続キャッシュを欠落 | ホスティング環境を最初に確認 |
| PHP のみを確認し JS ポーリングを見落とす | JS setInterval + fetch = Self-DDoS | ポーリングパターン用の .js ファイルを確認 |
詳細リファレンス
タスクに基づいてこれらのリファレンスをロード:
| タスク | ロードするリファレンス |
|---|---|
| PHP コードの問題確認 | references/anti-patterns.md |
| WP_Query 呼び出しの最適化 | references/wp-query-guide.md |
| キャッシング実装 | references/caching-guide.md |
| 高トラフィックイベント準備 | references/measurement-guide.md |
注記: 標
ライセンス: MIT(寛容ライセンスのため全文を引用しています) · 原本リポジトリ
詳細情報
- 作者
- jorgerosal
- ライセンス
- MIT
- 最終更新
- 2026/4/17
Source: https://github.com/jorgerosal/wordpress-skills / ライセンス: MIT