pre_get_postsでメインクエリを正しく書き換える方法【WordPress】

WordPress

こんにちは!コーダーのゆうしです。

WordPressで「カスタム投稿の一覧ページに表示する件数を変えたい」「特定のカテゴリだけ除外したい」といった要件はよく出てきます。こういうときに query_posts() を使っているコードを見かけることがありますが、実はこれはNGな方法です。

今回はメインクエリを正しく書き換えられる pre_get_posts フックの使い方を、query_posts() がダメな理由とあわせて解説します。

メインクエリとは何か

WordPressはページが表示されるとき、URLに応じたSQLクエリを自動で生成・実行して投稿を取得しています。この自動で走るクエリをメインクエリと呼びます。

たとえばブログの一覧ページ(home.php や archive.php)では「投稿タイプが post で、新しい順に10件取得する」といったクエリがWordPressによって自動で実行されます。

テンプレート上で have_posts() や the_post() を使っているループがこのメインクエリの結果を出力しています。

query_posts()がNGな理由

昔のWordPressのコードや古いチュートリアルでは、メインクエリを変えるために query_posts() を使う例が見られます。しかし現在はこの方法は推奨されていません。

パフォーマンスが悪くなる

query_posts() を呼び出すと、WordPressはいったんメインクエリを実行したあとにもう一度クエリを実行します。つまりSQLが二重に走ります。これはパフォーマンス上の無駄です。

他の機能への影響が出やすい

query_posts() はグローバルな $wp_query を上書きします。これにより is_category() や is_archive() といった条件分岐タグの動作がおかしくなったり、ページネーションが壊れたりします。

⚠️ WordPressの公式ドキュメントでも「query_posts() はメインループを書き換えるためには使わないこと」と明記されています。

pre_get_postsとは

pre_get_posts はWordPressがSQLを実行する前に、クエリの内容を書き換えられるアクションフックです。メインクエリのオブジェクト($query)を直接変更できるため、二重クエリが発生せずパフォーマンス的にもクリーンです。

使う場所は functions.php です。テンプレートファイル(archive.php など)に書いてもタイミングが遅すぎて効かないので注意してください。

基本的な書き方

// functions.php
function my_pre_get_posts( $query ) {
  // 管理画面とフィードには適用しない(後述)
  if ( $query->is_admin() || $query->is_feed() ) {
    return;
  }

  // メインクエリのみに適用する
  if ( $query->is_main_query() ) {
    // ここでクエリを変更する
    $query->set( 'posts_per_page', 6 );
  }
}
add_action( 'pre_get_posts', 'my_pre_get_posts' );

ポイントは2つあります。

  • $query->is_main_query() でメインクエリかどうかを判定してから変更する
  • $query->is_admin() で管理画面への影響を防ぐ

この2つの条件を入れないと意図しないページにまで変更が適用されてしまうので、必ずセットで書くようにしましょう。

よく使うカスタマイズ例

表示件数を変える

カスタム投稿タイプ「news」のアーカイブページだけ表示件数を6件にする例です。

function my_pre_get_posts( $query ) {
  if ( $query->is_admin() || $query->is_feed() ) {
    return;
  }

  if ( $query->is_main_query() && $query->is_post_type_archive( 'news' ) ) {
    $query->set( 'posts_per_page', 6 );
  }
}
add_action( 'pre_get_posts', 'my_pre_get_posts' );

is_post_type_archive() でカスタム投稿タイプのアーカイブページを指定できます。ブログ一覧(is_home())や特定のカテゴリ(is_category())など、条件分岐タグを組み合わせることで柔軟に絞り込めます。

特定のカテゴリを除外する

ブログ一覧からカテゴリID「5」の投稿を除外する例です。

function my_pre_get_posts( $query ) {
  if ( $query->is_admin() || $query->is_feed() ) {
    return;
  }

  if ( $query->is_main_query() && $query->is_home() ) {
    // カテゴリIDに - をつけると除外になる
    $query->set( 'cat', '-5' );
  }
}
add_action( 'pre_get_posts', 'my_pre_get_posts' );

💡 カテゴリIDをマイナスにするとそのカテゴリを除外できます。複数除外したい場合は '-5,-8' のようにカンマ区切りで指定してください。

カスタム投稿タイプをブログ一覧に含める

トップページのブログ一覧(is_home())に、通常の投稿とカスタム投稿「news」を混ぜて表示する例です。

function my_pre_get_posts( $query ) {
  if ( $query->is_admin() || $query->is_feed() ) {
    return;
  }

  if ( $query->is_main_query() && $query->is_home() ) {
    $query->set( 'post_type', [ 'post', 'news' ] );
  }
}
add_action( 'pre_get_posts', 'my_pre_get_posts' );

is_admin()とis_feed()を忘れずに

pre_get_posts はフロントエンドだけでなく管理画面や RSS フィードのクエリにも発火します。条件を絞らないと投稿管理画面の一覧まで変わってしまうので、必ず除外条件を書いてください。

条件役割
$query->is_admin()管理画面のクエリには適用しない
$query->is_feed()RSSフィードのクエリには適用しない
$query->is_main_query()WP_Queryなどのサブクエリには適用しない

is_admin() はグローバル関数でも使えますが、pre_get_posts の中では $query->is_admin() を使うのが正しい書き方です。グローバルの is_admin() だと AJAX リクエスト時に意図しない動作になることがあります。

躓きやすいポイント

テンプレートファイルに書いても効かない

 NG 

// archive.php に書いてしまっている
add_action( 'pre_get_posts', function( $query ) {
  $query->set( 'posts_per_page', 6 );
});

 OK 

// functions.php に書く
add_action( 'pre_get_posts', function( $query ) {
  if ( $query->is_admin() || $query->is_feed() ) return;
  if ( $query->is_main_query() ) {
    $query->set( 'posts_per_page', 6 );
  }
});

pre_get_posts はクエリが実行される前に発火するフックです。テンプレートファイルが読み込まれる時点ではすでにクエリが完了しているため、そこで add_action しても間に合いません。必ず functions.php に書いてください。

is_main_query()を書き忘れてサブループまで変わる

 NG 

function my_pre_get_posts( $query ) {
  // is_main_query()なしだとWP_Queryにも適用されてしまう
  if ( $query->is_home() ) {
    $query->set( 'posts_per_page', 6 );
  }
}
add_action( 'pre_get_posts', 'my_pre_get_posts' );

is_main_query() を書かないと、同じページ内で WP_Query を使ったサブループにも変更が適用されてしまいます。サブループの表示件数が意図せず変わる、といった症状が出たらこれが原因の可能性が高いです。

まとめ

メインクエリを書き換えたいときは pre_get_posts を使う、これを習慣にしておけば query_posts() による二重クエリやページネーション崩れといったトラブルを防げます。

  • 書く場所は functions.php
  • is_admin()is_feed() で管理画面・フィードを除外する
  • is_main_query() でサブループへの影響を防ぐ

ご不明な点やコーディングのご依頼はお問い合わせからお気軽にどうぞ。

タイトルとURLをコピーしました