「お知らせ一覧は1ページ10件にしたい」「カテゴリーアーカイブは古い順に並べたい」「検索結果から特定の投稿タイプを除外したい」。こうしたとき、テンプレートの中で query_posts() を使っていませんか。昔の解説記事には頻繁に登場しますが、公式ドキュメントでも可能な限り避けるべき関数とされています。
代わりに使うのが pre_get_posts というアクションフックです。この記事では、メインクエリを安全に書き換える方法と、query_postsを避けるべき理由、get_postsやWP_Queryとの使い分けを解説します。
メインクエリとは
WordPressは、ページにアクセスがあると、URLをもとに「どの記事を取得するか」を判断して、データベースに問い合わせを行います。このとき自動で実行されるのがメインクエリです。トップページ、カテゴリーアーカイブ、検索結果、投稿の個別ページなど、すべての表示がこのメインクエリを元にしています。
テンプレートで have_posts() と the_post() を使ったループは、このメインクエリの結果を表示しています。つまり、表示件数や並び順を変えたい場合は、メインクエリの条件そのものを変えるのが本来の方法です。
query_postsを使ってはいけない理由
query_posts()は、テンプレートの中でメインクエリを上書きする関数です。手軽に見えますが、次の問題があります。
- クエリが二重に実行される:WordPressが自動で実行したあとに、もう一度クエリを実行するため、無駄な負荷がかかる。
- ページネーションが壊れやすい:件数やページ数の情報が正しく引き継がれず、2ページ目以降が表示されなくなる。
- グローバル変数を書き換える:後続の処理やプラグインに、予期しない影響を与える。
- リセットが必要:wp_reset_query()を忘れると、サイドバーやフッターの表示が崩れる。
WordPressの公式ドキュメントでも、query_postsは「使用を避けるべき」とされています。メインクエリを変更するときは、pre_get_postsを使います。
pre_get_postsの基本
pre_get_postsは、クエリがデータベースに問い合わせを行う直前に呼ばれるフックです。ここでクエリの条件を変更すれば、最初から目的の条件で、1回だけクエリが実行されます。
add_action( 'pre_get_posts', 'my_modify_main_query' );
function my_modify_main_query( $query ) {
// 管理画面と、メインクエリ以外は対象外にする
if ( is_admin() || ! $query->is_main_query() ) {
return;
}
// お知らせのアーカイブだけ、表示件数を10件にする
if ( $query->is_post_type_archive( 'news' ) ) {
$query->set( 'posts_per_page', 10 );
}
}
この書き方のポイントは、次の3点です。
- 関数の引数
$queryは、参照渡しのため、set()で変更すればそのまま反映される(return不要) is_admin()で、管理画面のクエリを除外する$query->is_main_query()で、サブクエリ(ウィジェットなど)を除外する
特に2と3の判定は、必ず書いてください。抜けていると、管理画面の投稿一覧や、ウィジェットのクエリまで書き換わり、原因の分かりにくい不具合になります。
よく使うパターン集
表示件数を変える
if ( $query->is_post_type_archive( 'news' ) ) {
$query->set( 'posts_per_page', 10 );
}
並び順を変える(古い順)
if ( $query->is_category() ) {
$query->set( 'orderby', 'date' );
$query->set( 'order', 'ASC' );
}
検索結果の対象を絞る
if ( $query->is_search() ) {
$query->set( 'post_type', array( 'post', 'news' ) );
}
特定のカテゴリーを除外する
if ( $query->is_home() ) {
$query->set( 'category__not_in', array( 5 ) );
}
カスタムフィールドで並べ替える
if ( $query->is_post_type_archive( 'event' ) ) {
$query->set( 'meta_key', 'event_date' );
$query->set( 'orderby', 'meta_value' );
$query->set( 'order', 'ASC' );
}
どのパターンも、まず条件分岐で「どのページのクエリか」を限定し、そのうえで set() を呼ぶ形が共通しています。
条件分岐タグの使いどころ
pre_get_postsの中では、is_home() や is_category() などの条件分岐タグを使えます。ただし、実行のタイミングがテンプレート読み込みの前のため、グローバルな条件分岐ではなく、$queryのメソッドを使うのが安全です。
| 用途 | 推奨する書き方 |
|---|---|
| トップページ | $query->is_home() |
| カテゴリー | $query->is_category() |
| 検索結果 | $query->is_search() |
| 投稿タイプのアーカイブ | $query->is_post_type_archive( 'news' ) |
| タクソノミーのアーカイブ | $query->is_tax( 'news_cat' ) |
フロントページを固定ページにしている場合は、is_home() の意味が変わる点にも注意が必要です。トップページに最新記事を出したいのか、投稿ページ(ブログ)を指しているのかを、設定画面で確認しておきましょう。
get_posts・WP_Query・pre_get_postsの使い分け
クエリを扱う方法は複数あり、混乱しやすい部分です。目的別に整理します。
| やりたいこと | 使うもの |
|---|---|
| そのページのメインの一覧を変更する | pre_get_posts |
| ページ内に「関連記事」「新着」を別枠で出す | WP_Query(ループ付き) |
| 記事の配列を取得するだけ(ループ不要) | get_posts() |
| メインクエリを書き換える | query_postsは使わない |
判断の基準は、「そのページが本来表示すべき一覧を変えたいのか、それとも別の一覧を追加で表示したいのか」です。前者はpre_get_posts、後者はWP_Queryです。
なお、get_posts()は内部でWP_Queryを使っていますが、デフォルトでは投稿の固定表示(sticky)を無視するなど、細かい挙動の違いがあります。また、suppress_filters がtrueになっているため、多くのフィルターの影響を受けません。ただし、pre_get_posts はこの設定にかかわらず実行されるため、is_main_query() の判定を入れずに書き換えると、get_posts()の結果にも影響します。
ハマりやすいポイント
1. 固定ページでは意味がない
固定ページのクエリは、特定のページを1件取得するものです。件数や並び順を変えても意味がありません。固定ページ内に一覧を出す場合は、WP_Queryを使います。
2. 条件分岐を絞らないと全体に影響する
pre_get_postsは、サイトの全ページで実行されます。条件分岐を書かずに set( 'posts_per_page', 3 ) と書くと、トップページも検索結果もカテゴリーも、すべて3件になります。必ず対象のページを絞りましょう。
3. 優先度とプラグインの影響
プラグインも同じフックで書き換えを行っていることがあります。思った通りにならない場合は、フックの優先度(第3引数)を調整するか、他のプラグインを一時的に停止して確認します。
4. ページネーションの件数が合わない
posts_per_pageを変更しても、WordPress標準の「ブログページに表示する最大投稿数」の設定とは別に動きます。ページ送りのリンクが想定と違うときは、設定値と、pre_get_postsの値の両方を確認してください。
まとめ
メインクエリの変更は、pre_get_postsで行うのが現在の正しい方法です。
- query_postsは、二重クエリやページネーション崩れの原因になるため、使わない
- pre_get_postsは、
is_admin()とis_main_query()の判定を必ず入れる - 対象ページを
$query->is_xxx()で絞ってから、set()で条件を変更する - 別枠の一覧はWP_Query、配列だけ欲しいときはget_posts()を使う
古い記事を参考にすると、query_postsが登場することがあります。そのまま真似せず、pre_get_postsに置き換えて実装するようにしてください。

コメント