posts_per_page が効かない原因と pre_get_posts の正しい書き方

結論

posts_per_page を指定したのに件数が変わらないときは、次の順で疑います。

  1. WP_Query に numberposts を渡している(WP_Query では numberposts は無効)
  2. pre_get_posts で、条件なしに posts_per_page を上書きしている(サブクエリや get_posts にも効く)
  3. メインクエリの件数を変えたいのに、テンプレート側で new WP_Query している
  4. 設定>表示設定の「1ページに表示する最大投稿数」が使われている(引数を渡していない)

メインクエリの件数を変えるなら、テンプレートで new WP_Query せず、pre_get_posts の中で $query->is_main_query() と is_admin() を確認してから set() します。

原因・仕組み

WP_Query は posts_per_page を渡さないと、設定>表示設定の値(既定 10)を使います。numberposts は get_posts() 専用の別名で、WP_Query に渡しても読まれません。

pre_get_posts は、クエリオブジェクトが作られた後、SQL を実行する前に走るアクションです。引数 $query は参照渡しで、set() で条件を書き換えられます。公式リファレンスでは、管理画面を巻き込まないよう is_admin() を確認し、メインクエリだけを対象にするため、グローバル関数の is_main_query() ではなく $query->is_main_query() を使うよう説明されています。

条件なしで書くと、同じページにあるウィジェットのクエリ、get_posts()、関連記事のクエリなど、あらゆる WP_Query が書き換わります。

手順(サンプルコード)

悪い例(そのままコピーしないでください)

// 悪い例: すべてのクエリが10件に書き換わる
add_action( 'pre_get_posts', function ( $query ) {
    $query->set( 'posts_per_page', 10 );
} );

良い例(メインクエリだけ、フロントのカスタム投稿タイプ一覧だけ)

add_action( 'pre_get_posts', function ( $query ) {
    if ( is_admin() || ! $query->is_main_query() ) {
        return;
    }
    if ( $query->is_post_type_archive( 'ks_book' ) ) {
        $query->set( 'posts_per_page', 12 );
    }
} );

条件の判定には、グローバル関数ではなく $query->is_post_type_archive() のようなメソッドを使います。pre_get_posts の時点ではグローバルの $wp_query がまだ確定していないことがあるためです。

サブクエリは引数で渡す

$q = new WP_Query( array(
    'post_type'      => 'post',
    'posts_per_page' => 3,   // numberposts ではなくこちら
) );

動作確認(検証環境と結果)

WordPress 7.1.2(日本語)、PHP 8.2.12、テーマ twentytwentyfive。表示設定の posts_per_page は 10、検証用の公開記事は12件です(testsposts-per-page-not-working-pre-get-posts.md に全文)。

確認内容 結果
WP_Query(posts_per_page 指定なし) 10件(表示設定の値)
WP_Query に numberposts=3 10件(効かない)
WP_Query に posts_per_page=3 3件
WP_Query に posts_per_page=-1 12件(全件)
条件なしの pre_get_posts で 10 に上書き後、posts_per_page=3 のクエリ 10件(上書きされた)
同じ状態で get_posts( numberposts=2 ) 10件(get_posts にも効く)
is_admin() と is_main_query() を確認する pre_get_posts にした後、posts_per_page=3 のクエリ 3件(巻き込まれない)
手作りの new WP_Query() の is_main_query() false

get_posts() は既定でクエリ系フィルター(posts_where など)を無効にしますが、pre_get_posts は無効になりませんでした。「get_posts だから pre_get_posts の影響を受けない」と考えるのは間違いです。

posts_per_page が効かない原因と pre_get_posts の正しい書き方の注意点

  • posts_per_page に -1 を渡すと全件を取得します。件数が多いサイトでは負荷が高くなるため、上限を決めてください。
  • メインクエリの件数を変えたあと、ページ数が変わるため、パーマリンクの /page/N/ が404になる場合があります。フロント側のページネーションも合わせて確認してください。
  • pre_get_posts で posts_per_page を変えても、設定>表示設定の値は変わりません。
  • 管理画面の一覧(edit.php)にも pre_get_posts は走ります。is_admin() を必ず確認してください。

posts_per_page が効かない原因と pre_get_posts の正しい書き方でよくあるミス

  • テーマの一覧ページで query_posts() を使い、メインクエリを上書きする(query_posts() の使い方|説明・引数・注意点)。
  • if ( is_main_query() ) とグローバル関数で書いて、条件がずれる。
  • カスタム投稿タイプのアーカイブで件数を変えたいのに、投稿タイプの判定を入れず、全アーカイブが変わる。
  • 検証時にキャッシュプラグインが古い出力を返し、変更が反映されないと誤解する。

posts_per_page が効かない原因と pre_get_posts の正しい書き方のチェックリスト

  • WP_Query に渡す件数の引数は posts_per_page か。
  • pre_get_posts の先頭で is_admin() と $query->is_main_query() を確認しているか。
  • 対象のアーカイブや投稿タイプで絞り込んでいるか。
  • ページ送りの URL(/page/2/ など)が404にならないか確認したか。

posts_per_page が効かない原因と pre_get_posts の正しい書き方のFAQ(よくある質問)

Q. offset を使うとページネーションが壊れます。
A. offset を指定すると、paged による位置計算が使われなくなります。ページネーションが必要なら、offset ではなく pre_get_posts と paged の組み合わせで設計してください。

Q. posts_per_page を get_option で取りたい。
A. get_option( 'posts_per_page' ) で取れます。検証環境では 10 でした。

Q. 特定のカテゴリだけ件数を変えたい。
A. pre_get_posts の中で $query->is_category( 'news' ) のように判定してから set() します。

筆者の見解(posts_per_page が効かない原因と pre_get_posts の正しい書き方)

私見では、pre_get_posts は便利な反面、影響範囲が最も広いフックのひとつです。「メインクエリか」「管理画面でないか」「どの一覧か」の3条件を、毎回セットで書く癖をつけるのがよいと考えます。件数を変えたいときにテンプレートで新しい WP_Query を作る書き方は、ページネーションの不具合につながりやすいので、避けたほうが安全です。

posts_per_page が効かない原因と pre_get_posts の正しい書き方の関連項目

出典(一次情報)

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