2026年5月20日
2026年5月20日
WordPressのページネーションが404になる原因とpre_get_postsでの直し方
ブログ一覧やカテゴリーのアーカイブで「2ページ目以降が404になる」「ページ番号の数が実際の件数と合わない」という症状は、多くの場合、WordPress のメインクエリと、テンプレートの中で作った別のクエリの件数がずれていることが原因です。この記事では、仕組みと直し方をコード付きで説明します。関数の動作は、2026年10月時点の WordPress 開発者リファレンスとコアのソースコードで確認しています。
症状
/page/2/などの2ページ目以降を開くと404になる- ページ番号のリンクが、実際の件数より多い、または少ない
- 最後のページ番号を押すと404になる、または空のページが出る
offset(先頭の何件かを飛ばす指定)を入れたら、ページ送りがおかしくなった
なぜずれるのか
WordPress は URL を読み取ると、テンプレートを読み込む前にメインクエリを実行します。表示件数は「設定」→「表示設定」の「1ページに表示する最大投稿数」(初期値は10件)です。
2ページ目以降の URL で、メインクエリの結果が0件だと、WordPress はそのページを404にします(コアの WP::handle_404() の動作)。テンプレートの中で別の WP_Query を作って件数を変えていても、404かどうかはメインクエリで決まります。
たとえば、表示設定が10件のまま、テンプレートの中で posts_per_page => 5 の WP_Query を作ったとします。投稿が25件なら、テンプレートのクエリは5ページ分のリンクを出しますが、メインクエリは3ページまでしかありません。そのため4ページ目と5ページ目が404になります。
また、paginate_links() は引数の total を省略すると、メインクエリ($wp_query->max_num_pages)のページ数を使います。別のクエリで一覧を出しているのに total を渡していないと、ページ数が合わなくなります。
直し方1: 件数はpre_get_postsでメインクエリを変える
一覧の件数や投稿タイプを変えたいだけなら、テンプレートで新しい WP_Query を作らず、pre_get_posts アクションでメインクエリそのものを変えます。メインクエリの件数が変わるので、404の判定もページ数も一致します。
子テーマの functions.php か、コードスニペット用のプラグインに追加します。
<?php
add_action( 'pre_get_posts', function ( WP_Query $query ): void {
// 管理画面と、メイン以外のクエリ(ウィジェットやテンプレート内の WP_Query など)は変えない
if ( is_admin() || ! $query->is_main_query() ) {
return;
}
// 投稿一覧(ブログのトップ)とカテゴリーアーカイブを12件ずつにする
if ( $query->is_home() || $query->is_category() ) {
$query->set( 'posts_per_page', 12 );
}
} );
pre_get_postsは、クエリの変数が用意された後、データベースへの問い合わせの前に実行されます。WP_Queryのすべてのクエリで実行されるため、$query->is_main_query()でメインクエリだけに絞ります。- 条件分岐は、関数版の
is_home()ではなく、渡されたオブジェクトのメソッド$query->is_home()を使います。開発者リファレンスによると、pre_get_postsの時点ではis_front_page()は正しく動かず、is_home()は動きます。 - テンプレート側は、通常のループ(
have_posts()/the_post())とthe_posts_pagination()のままで正しいページ数になります。
pre_get_posts の使い方全般は、WordPressのpre_get_postsでメインクエリを変更する方法で説明しています。
直し方2: カスタムWP_Queryでは件数とtotalを合わせる
固定ページのテンプレートで投稿一覧を出す場合など、別の WP_Query が必要なときは、次の2点を守ります。
- 現在のページ番号を取り、
pagedに渡す paginate_links()のtotalに、そのクエリのmax_num_pagesを渡す
<?php
// 静的なフロントページではページ番号が 'page' に入るため、両方を見る
$paged = max( 1, (int) get_query_var( 'paged' ), (int) get_query_var( 'page' ) );
$news_query = new WP_Query( array(
'post_type' => 'post',
'posts_per_page' => 12,
'paged' => $paged,
) );
if ( $news_query->have_posts() ) {
while ( $news_query->have_posts() ) {
$news_query->the_post();
the_title( '<h2><a href="' . esc_url( get_permalink() ) . '">', '</a></h2>' );
}
$big = '999999999'; // URL の中で置き換えるための、ありえない番号
echo paginate_links( array(
'base' => str_replace( $big, '%#%', esc_url( get_pagenum_link( $big ) ) ),
'format' => '?paged=%#%',
'current' => $paged,
'total' => $news_query->max_num_pages,
) );
}
wp_reset_postdata();
get_query_var()の開発者リファレンスでは、静的なフロントページ(固定ページをホームページに設定した場合)では、ページ番号をpageから取る例が示されています。上のコードはpagedとpageの大きいほうを使います。- 固定ページのテンプレートでは、メインクエリはその固定ページ1件です。この場合は一覧の件数による404は起きにくく、ずれの原因は
pagedとtotalの渡し忘れであることが多いです。 - 投稿一覧やアーカイブのテンプレートの中で、件数の違う
WP_Queryを作るのは避けてください。前の節のとおり、メインクエリの件数で404が決まるためです。直し方1に移してください。
直し方3: offsetを使うときはfound_postsも直す
「先頭の3件はピックアップ欄に出すので、一覧からは除きたい」といった理由で offset を使うと、ページ送りが壊れます。WP_Query は paged から offset を計算しており、自分で offset を指定すると、どのページでも同じ位置から取得してしまうためです。開発者リファレンスでも、offset とページ送りを両立させるには自分で処理する必要があると書かれています。
次のコードは、ブログのトップの一覧から先頭3件を除き、ページ送りを正しく保ちます。
<?php
const WPDR_HOME_OFFSET = 3;
add_action( 'pre_get_posts', function ( WP_Query $query ): void {
if ( is_admin() || ! $query->is_main_query() || ! $query->is_home() ) {
return;
}
$per_page = (int) get_option( 'posts_per_page' );
$paged = max( 1, (int) $query->get( 'paged' ) );
// 1ページ目は3件飛ばし、2ページ目以降はその分もずらす
$query->set( 'offset', WPDR_HOME_OFFSET + ( $paged - 1 ) * $per_page );
} );
add_filter( 'found_posts', function ( int $found_posts, WP_Query $query ): int {
if ( ! is_admin() && $query->is_main_query() && $query->is_home() ) {
// 総件数から飛ばした3件を引き、ページ数の計算を合わせる
return max( 0, $found_posts - WPDR_HOME_OFFSET );
}
return $found_posts;
}, 10, 2 );
found_postsフィルターは、クエリが数えた総件数を書き換えます。max_num_pagesは、この件数を1ページの件数で割って計算されるため、ここを直すとページ数も合います。- ここで件数を変えるなら、
posts_per_pageもpre_get_postsの中で同じ値にsetし、$per_pageにはその値を使ってください。
直し方4: パーマリンクのルールを作り直す
カスタム投稿タイプを追加した後などに /page/2/ だけが404になる場合は、リライトルール(URL の解釈規則)が古いままのことがあります。管理画面の「設定」→「パーマリンク」を開き、何も変えずに「変更を保存」を押すと作り直されます。WP-CLI が使える場合は次のコマンドでも同じです。
wp rewrite flush
--hard を付けると .htaccess も書き直します(Apache の場合)。サーバーで .htaccess を手で編集している場合は、付ける前に内容を控えてください。
やってはいけない書き方
query_posts()でメインクエリを置き換える: 開発者リファレンスでも使わないよう案内されています。メインクエリの情報が上書きされ、ページ送りや条件分岐が崩れる原因になります。pre_get_postsを使ってください。- テンプレートで件数を変えた
WP_Queryを作り、the_posts_pagination()だけを置く:the_posts_pagination()はメインクエリのページ数でリンクを出すため、一覧と合いません。 pre_get_postsでis_main_query()の確認を省く: ウィジェットの最近の投稿や、テンプレート内の別のクエリまで件数が変わります。
確認の手順
- 変更後、ブラウザーで1ページ目・2ページ目・最後のページを開き、404にならず、件数が想定どおりか確かめます。
- 最後のページの次の番号(例: 最後が5ページ目なら
/page/6/)を直接開き、404になることを確かめます。ここが200で空のページになる場合は、別のクエリで一覧を出していないか見直します。 - キャッシュ系のプラグインを使っている場合は、キャッシュを削除してから確認します。
まとめ
- 2ページ目以降の404は、メインクエリの件数で決まる。一覧の件数を変えるときは、テンプレートで
WP_Queryを作らず、pre_get_postsでメインクエリを変える。 - 別の
WP_Queryが必要なときは、pagedを渡し、paginate_links()のtotalにそのクエリのmax_num_pagesを渡す。静的なフロントページではpageも見る。 offsetを使うときは、ページごとにoffsetを計算し、found_postsフィルターで総件数から飛ばした件数を引く。query_posts()は使わない。