件数・現在ページ・総ページ数・リンクを同じクエリでつなぐ
WP_Queryでページ送りを作るには、posts_per_pageで1ページの件数を決め、現在ページをpagedへ渡します。リンク側では同じ現在ページをcurrent、独自クエリのmax_num_pagesをtotalへ渡します。
4つの値を同じクエリでそろえると、1ページ目と2ページ目の取得結果、現在位置、最終ページが一致します。
ページネーションは4つの値でできている
表示件数だけ指定しても、WP_Queryは現在が何ページ目か分かりません。ページリンクを表示するだけでは投稿は切り替わらず、取得側へ現在ページを渡す必要があります。件数・現在ページ・総ページ数・リンクを、次のようにつなぎます。
| 役割 | WP_Queryまたは関数 | 設定する値 |
|---|---|---|
| 1ページの件数 | WP_Queryのposts_per_page | 1以上の整数 |
| 現在ページの取得 | get_query_var( 'paged', 1 ) | URLに対応するページ番号 |
| 現在ページの取得条件 | WP_Queryのpaged | 取得したページ番号 |
| 総ページ数 | WP_Queryのmax_num_pages | 取得後に計算されたページ数 |
| ページリンク | paginate_links()のcurrentとtotal | 同じ現在ページと総ページ数 |
WP_Queryとpaginate_links()をつないだ完成例
次のコードは、検証用の通常投稿6件(ID 10〜15)を3件ずつ取得し、番号付きページネーションを表示します。画像・表でも同じ6件を使います。IDはご自身の検証環境の投稿へ置き換え、通常運用で全投稿を対象にするときはpost__inを外してください。
クラシックテーマの子テーマへtemplate-parts/paginated-posts.phpとして保存します。固定フロントページ以外の一覧用テンプレートで、表示したい場所のPHP処理へget_template_part( ‘template-parts/paginated-posts’ );を追加して呼び出してください。ファイルを保存するだけでは一覧は出ません。WordPress標準のアーカイブそのものを変更する場合は、後のメインクエリの例を使います。
クラシックテーマのテンプレート(またはテンプレートパーツ)で、一覧を表示したい位置に置く例です。投稿本文やfunctions.phpへ、そのまま貼るコードではありません。
<?php$current_page = max( 1, (int) get_query_var( 'paged', 1 ) ); $list_query = new WP_Query( [ 'post_type' => 'post', 'post_status' => 'publish', 'post__in' => [ 10, 11, 12, 13, 14, 15 ], 'orderby' => 'post__in', 'posts_per_page' => 3, 'paged' => $current_page, 'ignore_sticky_posts' => true, 'no_found_rows' => false, ]); if ( $list_query->have_posts() ) { echo '<ul>'; while ( $list_query->have_posts() ) { $list_query->the_post(); printf( '<li><a href="%1$s">%2$s</a></li>', esc_url( get_permalink() ), esc_html( get_the_title() ) ); } echo '</ul>'; $placeholder = '999999999'; $pagination = paginate_links( [ 'base' => str_replace( $placeholder, '%#%', get_pagenum_link( $placeholder, false ) ), 'format' => '?paged=%#%', 'current' => $current_page, 'total' => $list_query->max_num_pages, 'type' => 'list', 'prev_text' => '前へ', 'next_text' => '次へ', ] ); if ( $pagination ) { echo '<nav aria-label="投稿一覧のページ送り">'; echo wp_kses_post( $pagination ); echo '</nav>'; }} else { echo '<p>表示できる投稿はありません。</p>';} wp_reset_postdata();新しく作るパーツには、先頭の<?phpも含めて保存します。すでにPHPが始まっている場所へ追加するなら、開始タグは不要です。get_pagenum_link()は現在のパーマリンク設定に合わせたページURLの土台を作ります。第2引数をfalseにし、リンク生成前にはHTML用のエスケープをしません。既存のクエリ文字列がある場合のURL破損を避け、生成後のHTMLをwp_kses_post()で出力します。
paginate_links()は総ページ数が2未満ならnullを返すため、戻り値がある場合だけnavを表示しています。
999999999は実際のページ番号ではありません。ページURLの中でほかの数字と重なりにくい仮の番号として入れ、直後のstr_replace()で%#%へ置き換えます。paginate_links()は、この%#%へ1、2、3のようなページ番号を入れてリンクを作ります。
type=’list’にすると、リンクはulとliを含むリスト形式のHTMLで返ります。wp_kses_post()は、WordPressの記事本文で許可される安全なHTMLだけを残して出力する関数です。この例では、navの開始タグ、ページリンク、終了タグを順にechoし、ページ送り全体を1つのナビゲーションとして表示しています。
独自ループでthe_post()を使った後は、wp_reset_postdata()で後続のテンプレートタグがメイン側の投稿を参照する状態へ戻します。
完成例は本番と分けたWordPress環境で、1ページ目と2ページ目の投稿・URLを比べます。検証や公開に使うサーバーをこれから用意する場合は、XserverのPHP対応やバックアップ条件も確認できます。
posts_per_pageで1ページの表示件数を決める
posts_per_pageは、1ページにつきWP_Queryが取得する投稿数です。3を指定すれば、条件に一致する投稿を最大3件取得します。最終ページは残り件数だけになるため、常に3件とは限りません。
| 指定 | 動作 | ページ送り |
|---|---|---|
| 3 | 1ページ最大3件 | 総件数からページ数を計算 |
| 1 | 1ページ1件 | 投稿数と同じだけページができる |
| 未指定 | 表示設定の1ページ最大投稿数を使用 | サイト設定に依存 |
| 0 | 通常は未指定と同様に表示設定の値を使う | 0件指定にはならない |
| -1 | 全件取得 | 通常のページ分割をしない |
全件表示が必要でも、投稿が増えるページでは-1を安易に使わず、上限を決めます。通常のページ送りを作る記事では、1以上の整数を指定してください。
pagedへ現在ページ番号を渡す
通常のアーカイブや一覧URLでは、get_query_var( ‘paged’, 1 )から現在ページを取得します。1つ目の引数pagedは取得したい値の名前、2つ目の引数1は値がないときの初期値です。1ページ目では0になる構成もあるため、max( 1, … )で1以上にそろえます。
引数や処理の一部分だけを示した例です。このまま単独で貼らず、テンプレート内のWP_Queryとループへ組み込んでください。
$current_page = max( 1, (int) get_query_var( 'paged', 1 ) ); $args = [ 'posts_per_page' => 3, 'paged' => $current_page,];これは完成例から現在ページの取得と設定だけを抜き出したコードです。同じテンプレートパーツで取得した$current_pageを、WP_Queryのpagedとpaginate_links()のcurrentの両方へ渡します。取得側とリンク側の値を別々に計算しないことが重要です。
ページリンクのURLが2ページ目になっても、WP_Queryへpagedを渡していなければ取得条件は毎回1ページ目のままです。現在ページは、リンク側より先に取得側へ渡します。
表示件数と現在ページの役割が分かったら、取得条件をWP Query Builderで整理できます。画面で指定したページ番号は固定値なので、実装時はURLから取得した$current_pageへ置き換えます。
ページ送り用の取得条件を作る
posts_per_pageとpagedを指定し、no_found_rowsをfalseにした$argsを作ります。現在ページの取得とページリンクの表示は、この記事のテンプレートコードでつなぎます。
found_postsとmax_num_pagesの役割を分ける
WP_Queryの実行後は、post_count、found_posts、max_num_pagesで件数を確認できます。ページリンクのtotalへ渡すのは、総件数ではなく総ページ数です。6件を3件ずつ表示する例で、違いを見てみましょう。
| 値 | 意味 | 今回の6件の例 |
|---|---|---|
| post_count | 現在ページで取得した件数 | 各ページ3件 |
| found_posts | 条件に一致した総件数 | 6件 |
| max_num_pages | 総ページ数 | 6件を3件ずつで2ページ |
max_num_pagesは、総件数と1ページの件数から計算された結果です。追加した一覧を$list_queryで取得したなら、$list_query->max_num_pagesを使います。グローバル$wp_query->max_num_pagesを使うと、ブラウザで開いているページと追加した一覧の総ページ数が混ざります。
paginate_links()へcurrentとtotalを明示する
paginate_links()にはメインクエリを使う既定値があります。独自WP_Queryでは、現在ページと総ページ数を明示し、どのクエリのリンクかをそろえます。次は完成例のリンク生成部分の抜粋で、$current_pageと$list_queryを作った同じテンプレートパーツ内で使います。
引数や処理の一部分だけを示した例です。このまま単独で貼らず、テンプレート内のWP_Queryとループへ組み込んでください。
$placeholder = '999999999'; $pagination = paginate_links( [ 'base' => str_replace( $placeholder, '%#%', get_pagenum_link( $placeholder, false ) ), 'format' => '?paged=%#%', 'current' => $current_page, 'total' => $list_query->max_num_pages, 'type' => 'list', 'prev_text' => '前へ', 'next_text' => '次へ', ]);currentは現在ページ、totalは総ページ数です。typeをlistにするとリスト形式のHTMLが返ります。この抜粋は生成までなので、表示するときは最初の完成例と同じように、戻り値がある場合だけwp_kses_post( $pagination )をechoします。文字列連結そのものが問題なのではなく、HTMLを適切に扱って出力することが重要です。
| typeの指定 | 戻り値 | 使う場面 |
|---|---|---|
| plainまたは未指定 | リンクをつないだ文字列 | 自分で外側のHTMLを用意する |
| array | リンクごとの配列 | 1件ずつ加工して表示する |
| list | ulとliを含むHTML | 番号付きページ送りをリストとして表示する |
固定フロントページではpageも確認する
固定フロントページ上のページテンプレートで独自一覧を表示する場合、現在ページ番号がpagedではなくpageへ入る構成があります。WordPress公式のget_query_var()リファレンスも、固定フロントページではpageを使う例を示しています。
引数や処理の一部分だけを示した例です。このまま単独で貼らず、テンプレート内のWP_Queryとループへ組み込んでください。
$current_page = max( 1, (int) get_query_var( 'page', 1 ) ); $front_query = new WP_Query( [ 'post_type' => 'post', 'post_status' => 'publish', 'post__in' => [ 10, 11, 12, 13, 14, 15 ], 'orderby' => 'post__in', 'posts_per_page' => 3, 'paged' => $current_page, 'ignore_sticky_posts' => true, 'no_found_rows' => false, ]);ここではURLからページ番号を読むときにpageを使いますが、追加のWP_Queryへ渡すパラメーター名はpagedです。つまり「WordPressから読む名前」と「WP_Queryへ渡す名前」が異なります。front-page.phpで使う取得部分の例なので、表示ループとページリンクでは変数名を$front_queryへ揃え、リンクのcurrentには同じ$current_pageを渡します。トップページだけ2ページ目が同じになる場合は、「設定 → 表示設定」で固定フロントページかを確認し、pagedとpageの値をローカルで比べます。
既存のアーカイブ一覧は作り直さない
カテゴリー、検索、投稿タイプアーカイブなど、WordPressが元から用意する一覧そのものを変えたい場合は、テンプレートで同じ投稿を独自WP_Queryし直す前に、メインクエリの変更を検討します。
対象を限定したpre_get_postsでposts_per_pageを変更すれば、同じメインクエリのfound_postsとmax_num_pagesが更新されます。テーマのページネーションもメインクエリへつながるため、独自の現在ページと総ページ数を別管理せずに済むことがあります。次はアーカイブを有効にした投稿タイプnewsが登録済みの場合の例で、子テーマのfunctions.phpまたは専用プラグインへ1回だけ置きます。
function wptl_change_news_archive_count( WP_Query $query ) { if ( is_admin() || ! $query->is_main_query() ) { return; } if ( $query->is_post_type_archive( 'news' ) ) { $query->set( 'posts_per_page', 12 ); }}add_action( 'pre_get_posts', 'wptl_change_news_archive_count' );add_action()は、WordPressがpre_get_postsを実行したときに、wptl_change_news_archive_count()を呼ぶための登録です。関数の中で、管理画面ではない、メインクエリである、newsアーカイブである、という順に対象を絞っています。
この例では、テンプレート側の通常ループとページリンクをそのまま使います。追加した一覧を作る目的なら、最初の追加WP_Queryの完成例を使います。
正常なページ送りでは3つの指定を避ける
総ページ数を使って数字のリンクを出す場合、次の3つを加えるとページ送りが崩れる原因になります。最初の完成例では使わず、必要になったときに右の各記事で条件を確認してください。
| 指定 | 避ける理由 | 次に確認する記事 |
|---|---|---|
| no_found_rowsをtrue | 通常の総件数と総ページ数を計算しない | no_found_rowsの使い分け |
| posts_per_pageを-1 | 全件取得になりページ分割しない | 全件取得が重くなる理由 |
| offsetを固定値で追加 | pagedとの取得位置とページ数がずれる | offsetとページネーションの併用 |
no_found_rows=trueは、総件数を数える処理を省いて軽くする指定です。その代わり、通常のmax_num_pagesを計算できないため、ページ送りが必要な一覧では使いません。
最初はposts_per_pageとpagedだけで1ページ目・2ページ目を確認します。正常に動いたあとで絞り込みや並び順を1つずつ追加すると、どの条件で崩れたか追いやすくなります。WP Query Builderでは取得条件を作り、ページ番号とリンクはテンプレートでつなぐ
WP Query Builderでは、投稿タイプ、表示件数、並び順、絞り込みなど、WP_Queryへ渡す$argsを整理できます。ページネーションを使う場合は、総ページ数が必要なのでno_found_rowsを有効にしない構成を確認します。
現在URLからページ番号を読む処理、固定フロントページのpage判定、paginate_links()のHTML出力はテンプレート側で追加します。Builderで生成した$argsへpagedを渡し、同じクエリのmax_num_pagesをリンクへつなぎます。
Builderの画面操作は公式チュートリアルで詳しく説明しています。この記事では、生成後のコードとページリンクをWordPress側でつなぐ部分を扱います。
同じ6件で投稿と現在ページの切り替わりを比べる
6件の記事を3件ずつ表示すると、1ページ目は最初の3件、2ページ目は残りの3件になります。投稿の内容だけでなく、ページ番号の現在位置も一緒に切り替わるかを見てください。次の値と画面は、本文と同じ6件をローカルのWordPress 7.1/PHP 8.4.20で表示したものです。
| 確認項目 | 1ページ目 | 2ページ目 |
|---|---|---|
| paged | 1 | 2 |
| post_count | 3 | 3 |
| found_posts | 6 | 6 |
| max_num_pages | 2 | 2 |
| 投稿ID | 10、11、12 | 13、14、15 |
| ページ間の重複 | なし | なし |
まず1ページ目には、最初の3件が出ます。番号の「1」が現在位置です。

1ページ目はID 10・11・12です。全6件を3件ずつ表示するため、ページ送りは2ページまであります。
「2」のリンクを開くと、次の3件へ進み、現在位置も「2」へ移ります。

同じサイトの2ページ目はID 13・14・15です。総件数と総ページ数を保ったまま、次の3件へ進んでいます。
同じ総件数と総ページ数を保ちながら、ページごとの投稿IDが次の3件へ切り替わります。paginate_links()へcurrent=2とtotal=2を渡すと、現在ページ2のaria-currentを含むHTMLが生成されます。2枚は同じサイトの1ページ目と2ページ目を、それぞれ撮影したものです。検証環境では専用のURL変数fixture_pageでページを切り替えていますが、本文コードへその名前を追加する必要はありません。
固定フロントページについては、公式仕様に従いpageを取得元とするコードを分離しました。実サイトでは表示設定を確認し、対象トップページでpageとpagedの実値を記録してください。
実装後は最後のページまで確認する
変更範囲を広げる前に、次の順番で原因を絞ります。
ページ送りの確認順
- 11ページ目でposts_per_pageどおりの件数か確認する
- 22ページ目でURL、current、投稿IDが変わるか確認する
- 31ページ目と2ページ目で投稿IDの重複・欠落がないか確認する
- 4最終ページの件数とmax_num_pagesを確認する
- 5最終ページを1つ超えたURLの挙動を確認する
- 6固定フロントページならpageとpagedを別に記録する
記事の追加や削除で最終ページは変わります。テスト用の固定件数だけでなく、0件、1ページだけ、2ページ以上、最終ページが端数になる条件を確認してください。
設定時によくある間違い
実装や公開へ進む前に、見落としやすい項目をまとめて確認します。
ページ送りが動かないときに見直す項目
- posts_per_pageだけを指定し、現在ページをpagedへ渡していない
- 2ページ目リンクを作っただけで取得記事も切り替わると思っている
- 追加WP_Queryのリンクにメインクエリのmax_num_pagesを使っている
- paginate_links()のtotalへfound_postsを渡している
- 固定フロントページでもpagedだけを読んでいる
- ページ送りが必要なのにno_found_rowsをtrueにしている
- posts_per_page=-1で全件取得しながらリンクを出そうとしている
- offsetを追加したのに取得位置と総ページ数を補正していない
- 追加ループ後にwp_reset_postdata()を書いていない
リンクが出ない、2ページ目が同じ、404になるなどの症状がすでにある場合は、WP_Queryでページネーションが動かない原因で症状別に確認します。表示件数だけが合わない場合は、posts_per_pageの確認方法もご覧ください。
WP_Queryとpaginate_links()の公式資料
posts_per_pageとpagedで取得する範囲、max_num_pagesと現在ページをpaginate_links()へ渡す方法は、次の公式資料で確認できます。
WordPress公式
確認に使った公式ドキュメント
リンクと投稿の両方が切り替わるか確かめる
リンクを押してURLが変わるだけでなく、表示される投稿と現在ページの番号も変われば、取得側とリンク側がつながっています。投稿が少ない場合や増減した場合も、最後のページまで確かめてから本番へ反映しましょう。
本番へ反映する前に
- 検証環境で動作を確かめ、変更前のファイルへ戻せる
- 0件・1ページだけ・複数ページ・最終ページが端数の表示を確認した
- ページリンクのURL・現在位置・投稿が一緒に切り替わり、重複や欠落がない
- 一覧の後のタイトルや本文が元の記事のままである
- スマホとキーボードでページ番号を操作できる
この記事の検証情報
検証環境
- WordPress
- 7.1.4
- PHP
- 8.3.36
- テーマ
- wpq-media + DDEV
- 対象
- カスタム投稿 / taxonomy / meta_query
更新履歴
- 2026年10月15日 初回公開
この記事を活用する
あとで見返す・共有する
お気に入りに保存できます。役に立ったポイントは、いいねや共有で教えてください。







