まず除外したい投稿をIDで指定できるか確認する
おすすめ記事など、通常一覧から外したい投稿が分かっているなら、まずpost__not_inでそのIDを除外します。投稿の位置ではなく対象を指定できるため、投稿を追加しても同じ記事を除外しやすくなります。
「先頭から2件」のように位置で飛ばす必要がある場合だけoffsetを使います。その場合は、ページごとの取得開始位置と、ページ数の計算に使う総件数の両方を直します。
offsetは一覧の先頭から何件か飛ばしたいときに使う
たとえば、サイトの上部に「おすすめ記事」を2件表示し、その下に通常の記事一覧を置く画面を考えます。同じ2件を通常一覧へ重ねて出したくないため、一覧の先頭2件を飛ばしたくなることがあります。
offsetは「条件に合う投稿の先頭から何件飛ばして、取得を始めるか」を指定する値です。offset=2なら1件目と2件目を飛ばし、3件目から取得します。
この一覧にページ送りを付ける場合は、現在のページ番号を表すpagedも使います。先頭から飛ばす件数と、ページを移動したぶんの件数を、両方とも取得開始位置へ反映します。この例で使う3つの値を、先に整理しておきましょう。
| 値 | 役割 | この例 |
|---|---|---|
| offset | 先頭から飛ばす件数 | おすすめ記事ぶんの2件を飛ばす |
| posts_per_page | 1ページに表示する件数 | 通常一覧は3件ずつ表示 |
| paged | 現在のページ番号 | 1ページ目または2ページ目 |
offsetとpagedはどちらも取得開始位置に関係します。固定したoffsetを渡すと、pagedだけでは取得位置が次のページへ進みません。
WordPress公式リファレンスでも、offsetを使うとpagedによるページ送りが期待どおりに動かないため、手動の計算が必要だと案内されています。まずは一覧の表示で起きている問題を確認する
いま表示されている問題から、取得位置と総ページ数のどちらを先に調べるか決めます。同じ投稿が続くのか、最後だけ空になるのかを見て、次の表から確認する値を選びます。
| 画面で起きていること | 最初に見る値 | 考えられる原因 |
|---|---|---|
| 1ページ目と2ページ目に同じ投稿が出る | offsetとpaged | 固定offsetのまま取得位置が進んでいない |
| 1・2ページ目は分かれるが最後のページが空 | found_postsとmax_num_pages | おすすめ記事ぶんを総件数から引いていない |
| 投稿を追加したら最後のページが増えた | 取得IDとmax_num_pages | 除外後の件数が1ページの表示件数を超えた |
固定offsetでは2ページ目も同じ位置から始まる
次のコードは、おすすめ記事として使った先頭2件を飛ばし、その後の投稿を1ページ3件ずつ取得しようとした失敗例です。クラシックテーマで別の一覧を追加する場合の、取得処理だけを示します。すでにPHPが始まっているテンプレートの処理へ置く部分で、このコードだけでは投稿やページリンクは表示されません。標準アーカイブの一覧そのものを変える場合は、後半のメインクエリの例を使います。
画像と同じ結果を比べるため、検証用の8件(ID 53〜60)をID昇順で取得します。先頭2件は「はじめてのWordPress設定」「サイト公開前のチェック」です。続く3件(ID 55〜57)は画像・バックアップ・お問い合わせフォームの記事、最後の3件(ID 58〜60)はSEO・アクセス解析・更新作業の記事です。以下ではこの8件を、順に記事A〜Hと呼びます。IDはご自身の検証環境にある投稿へ置き換えてください。
クラシックテーマのテンプレート(またはテンプレートパーツ)で、一覧を表示したい位置に置く例です。投稿本文やfunctions.phpへ、そのまま貼るコードではありません。
$current_page = max( 1, (int) get_query_var( 'paged', 1 ) ); $list_query = new WP_Query( [ 'post_type' => 'post', 'post_status' => 'publish', 'post__in' => [ 53, 54, 55, 56, 57, 58, 59, 60 ], 'orderby' => 'ID', 'order' => 'ASC', 'ignore_sticky_posts' => true, 'posts_per_page' => 3, 'paged' => $current_page, 'offset' => 2, ]);get_query_var( ‘paged’, 1 )は、URLから現在のページ番号を取得します。2つ目の1は、ページ番号がないときに1ページ目として扱うための初期値です。(int)で整数へ直し、max()で1未満にならないようにしています。
ところが、このコードでは1ページ目でも2ページ目でもoffset=2のままです。そのため、どちらも3件目から同じ3投稿を取得します。URLは2ページ目へ変わっていても、通常一覧の中身は1ページ目と同じです。
| 指定・結果 | 1ページ目 | 2ページ目 |
|---|---|---|
| paged | 1 | 2 |
| offset | 2 | 2 |
| 取得開始位置 | 3件目 | 3件目 |
| 表示した投稿 | 記事C・記事D・記事E | 記事C・記事D・記事E |
次の画像は、共通のサンプルサイトで2ページ目を開いた画面です。ページ送りは「2」になっていますが、通常一覧は1ページ目と同じID 55・56・57です。下部には、比較用に1ページ目の取得IDも添えています。

現在ページは2ですが、赤枠の投稿は1ページ目と同じ55・56・57です。offsetが2のままになっています。
この状態では、paginate_links()やパーマリンク設定を直しても取得結果は変わりません。まずWP_Queryへ最終的に入ったoffsetを確認します。
直す場所は取得位置と総件数の2つ
先頭から常に2件を除外し、その後を3件ずつ表示する場合は、2ページ目で3件ぶん先へ進む必要があります。ページごとの取得開始位置を次のように計算します。
| 値 | 式 | 例 |
|---|---|---|
| 基本の除外件数 | base_offset | 2 |
| 1ページの件数 | posts_per_page | 3 |
| 現在ページ | paged | 1、2、3 |
実際のoffset | base_offset + ( paged - 1 ) × posts_per_page | 2、5、8 |
| ページ対象の総件数 | found_posts - base_offset | 8件なら6件 |
| 総ページ数 | 除外後総件数 ÷ posts_per_pageを切り上げ | 6件なら2ページ |
表のbase_offset、posts_per_page、pagedをPHPの変数へ置き換えると次のコードになります。先ほどのテンプレートパーツの取得処理と入れ替えます。この段階では取得位置だけを直します。総件数の補正は後の「最後の空ページ」の節で追加するため、まだページ送りの修正は完了していません。
クラシックテーマのテンプレート(またはテンプレートパーツ)で、一覧を表示したい位置に置く例です。投稿本文やfunctions.phpへ、そのまま貼るコードではありません。
$current_page = max( 1, (int) get_query_var( 'paged', 1 ) );$per_page = 3;$base_offset = 2;$actual_offset = $base_offset + ( ( $current_page - 1 ) * $per_page ); $list_query = new WP_Query( [ 'post_type' => 'post', 'post_status' => 'publish', 'post__in' => [ 53, 54, 55, 56, 57, 58, 59, 60 ], 'orderby' => 'ID', 'order' => 'ASC', 'ignore_sticky_posts' => true, 'no_found_rows' => false, 'posts_per_page' => $per_page, 'paged' => $current_page, 'offset' => $actual_offset, 'wptl_base_offset' => $base_offset, ]);pagedは、現在ページの表示とページリンクで同じ番号を使うために残しています。ただし、実際に何件目から取得するかはoffsetが決めます。この例では、1ページ目は2件、2ページ目は5件、3ページ目は8件を飛ばして取得します。つまり、取得を始める位置はそれぞれ3件目・6件目・9件目です。
取得位置だけを2、5、8へ変えると、1ページ目と2ページ目の重複は消えます。しかし、WordPressのfound_postsはLIMITとoffsetを適用する前の条件一致総数を基準にするため、先頭2件を除外した事実が総ページ数へ自動反映されません。
位置とページ数を順番に直す
- 1先頭から飛ばす件数と1ページの表示件数を決める
- 2現在のページ番号を取得する
- 3ページ番号に合わせてoffsetを2、5、8と進める
- 4found_postsから先頭2件ぶんを引く
- 5補正後のmax_num_pagesでページリンクを作る
次は、同じサイトの2ページ目で取得位置だけを直した画面です。通常一覧がID 58・59・60へ変わっています。まだ総件数は補正していないため、ページ送りには「3」が残っています。

緑枠は次の3件、ID 58・59・60です。取得位置の重複は解消しましたが、総ページ数はまだ3のままです。
既知の投稿を飛ばすならpost__not_inを優先する
「上部へ表示したおすすめ2件を、下の一覧では出さない」など、除外する投稿IDが分かるなら、位置をずらすoffsetよりpost__not_inを先に検討します。次はその代替案で、前のoffset付きクエリとは併用しません。おすすめ2件のIDは同じ53・54ですが、取得対象は8件に限定せず、サイトの公開投稿からこの2件を除外します。
クラシックテーマのテンプレート(またはテンプレートパーツ)で、一覧を表示したい位置に置く例です。投稿本文やfunctions.phpへ、そのまま貼るコードではありません。
$current_page = max( 1, (int) get_query_var( 'paged', 1 ) );$featured_ids = [ 53, 54 ]; $list_query = new WP_Query( [ 'post_type' => 'post', 'post_status' => 'publish', 'posts_per_page' => 3, 'orderby' => 'ID', 'order' => 'ASC', 'ignore_sticky_posts' => true, 'paged' => $current_page, 'post__not_in' => $featured_ids, ]);除外条件がSQLのWHEREへ入るため、found_postsとmax_num_pagesも除外後の条件を基準に計算されます。指定IDが現在の絞り込み条件に一致しない場合は、単に結果へ含まれません。
最後の空ページはfound_postsから先頭2件ぶんを引いて直す
offsetが必要な場合は、取得位置の修正に加えて、found_postsフィルターで先頭から常に除外する件数だけを引きます。次のコードは子テーマのfunctions.phpまたはサイト固有のプラグインへ1回だけ追加し、対象のWP_Queryが動く前に読み込ませます。引くのは、ページごとに増えるactual_offsetではなく、先頭から外す基本の2件です。
function wptl_adjust_offset_found_posts( $found_posts, WP_Query $query ) { $base_offset = $query->get( 'wptl_base_offset' ); if ( '' === $base_offset ) { return $found_posts; } return max( 0, (int) $found_posts - (int) $base_offset );}add_filter( 'found_posts', 'wptl_adjust_offset_found_posts', 10, 2 );このフィルターはwptl_base_offsetを持つ一覧だけを補正し、ほかの一覧の件数には触れません。wptl_は、このサイト用の関数や値だと見分けるための接頭辞です。実際に使うときは、自分のサイトで重ならない名前へ変更してください。
ページネーションに必要な総件数を計算するため、対象クエリでno_found_rowsをtrueにしません。補正後の$list_query->max_num_pagesをpaginate_links()のtotalへ渡します。
引数や処理の一部分だけを示した例です。このまま単独で貼らず、テンプレート内のWP_Queryとループへ組み込んでください。
$pagination = paginate_links( [ 'current' => $current_page, 'total' => $list_query->max_num_pages, 'type' => 'list', ]); if ( $pagination ) { echo wp_kses_post( $pagination );}この部分は同じテンプレートパーツの投稿一覧を表示した後へ置き、補正後のクエリの総ページ数をリンクへ渡します。独自ループでthe_post()を使った場合は、ループ直後のwp_reset_postdata()も必要です。ページURLのbaseとformatまで含めた作り方は、表示件数とページネーションの設定方法で詳しく説明しています。
取得位置と総件数の補正を組み合わせたら、本番と分けたWordPress環境で1ページ目・2ページ目・最終ページを確認します。検証や公開に使うサーバーをこれから用意する場合は、Xserverの料金・バックアップ条件も比較できます。契約だけでページ送りの計算が直るわけではありません。
メインクエリで使う場合も同じ2つを補正する
カテゴリーや投稿タイプアーカイブのメイン一覧をoffset付きにする場合は、対象を限定したpre_get_postsでページ別offsetを設定します。次はアーカイブを有効にした投稿タイプnewsが登録済みの場合の例です。子テーマのfunctions.phpまたは専用プラグインへ置き、独自クエリの例とは分けて使います。総件数の補正には前のfound_postsフィルターも登録します。
function wptl_offset_news_archive( WP_Query $query ) { if ( is_admin() || ! $query->is_main_query() ) { return; } if ( ! $query->is_post_type_archive( 'news' ) ) { return; } $per_page = 3; $base_offset = 2; $current_page = max( 1, (int) $query->get( 'paged' ) ); $actual_offset = $base_offset + ( ( $current_page - 1 ) * $per_page ); $query->set( 'posts_per_page', $per_page ); $query->set( 'offset', $actual_offset ); $query->set( 'wptl_base_offset', $base_offset );}add_action( 'pre_get_posts', 'wptl_offset_news_archive' );管理画面、追加クエリ、別のアーカイブを除外してから値を設定します。found_postsフィルターはwptl_base_offsetがある場合だけ動くため、この対象限定と組み合わせられます。
取得条件を組み直すときは、投稿タイプや件数などの基本条件をWP Query Builderで整理できます。offsetを使う場合は、ページ別の計算と総件数補正をこの記事のコードで別に実装します。
ページ送りの取得条件を整理する
投稿タイプ・件数・除外する投稿IDなどを指定して$argsを作れます。生成だけでページリンクやfound_postsの補正が完成するわけではありません。
最後のページが空なら総件数と総ページ数を比べる
8件のうち先頭2件を除外し、1ページ3件で表示する場合、実際の対象は6件なので2ページです。補正しなければfound_posts=8として3ページと計算され、3ページ目のリンクを開いても投稿がありません。
| 状態 | found_posts | max_num_pages | 3ページ目 |
|---|---|---|---|
| 補正なし | 8 | 3 | 0件の空ページ |
| 先頭2件を補正 | 6 | 2 | ページリンクを作らない |
| 1件追加後に補正 | 7 | 3 | 1件を表示 |

これはサイト画面ではなく確認値の表です。found_postsを8→6へ補正すると総ページ数は3→2となり、空の3ページ目へのリンクが不要になります。
取得IDが1・2ページ目で分かれていても、総ページ数が正しいとは限りません。投稿ID、found_posts、max_num_pagesを同じログへ記録します。
投稿を1件追加すると最後のページが増えることがある
除外後の総件数が1ページの件数で割り切れる境界では、投稿が1件増えると新しい最終ページができます。6件を3件ずつなら2ページですが、7件になれば3ページ目に1件が出ます。
新着順の一覧では、ページ1を見た後に新しい投稿が先頭へ追加されると、ページ2へ移動した時に投稿位置がずれることがあります。これはoffset補正だけでは固定できない、更新中データのページ分割の性質です。orderbyを明示し、同値時にIDでも順序が決まるようにします。閲覧中の完全なスナップショットが必要なら、カーソルや基準日時を使う別設計を検討します。
5つの値を同じログへ出す
画面だけでは取得位置と総件数を分けにくいため、現在ページ、実際のoffset、取得した投稿ID、総件数、総ページ数を同じ場所で記録します。ページ別offsetを計算するテンプレートパーツで、$list_query = new WP_Query( … );の直後へ次のログを一時的に置きます。1ページ目と2ページ目の値を並べると、どこまで直ったかを判断できます。
error_log( wp_json_encode( [ 'paged' => $current_page, 'offset' => $actual_offset, 'post_ids' => wp_list_pluck( $list_query->posts, 'ID' ), 'found_posts' => $list_query->found_posts, 'max_num_pages' => $list_query->max_num_pages, ], JSON_UNESCAPED_UNICODE ));ローカルまたはステージング環境でWP_DEBUG・WP_DEBUG_LOGを有効にし、画面へ出力しないようWP_DEBUG_DISPLAYはfalseにします。通常はwp-content/debug.logへ記録されます。1ページ目と2ページ目のログを並べ、offsetがページ分だけ増え、投稿IDが重ならず、found_postsとmax_num_pagesが同じかを確認します。確認用コードとログは原因特定後に削除し、本番では有効にしないでください。
同じ8件で、重複と空ページが解消するか比べる
「同じ投稿が出る問題」と「空の最終ページが残る問題」は、次の表で別々に見てください。先頭の2件を飛ばすと、1ページ目は55・56・57から始まります。値は本文と同じ8件を使い、ローカルのWordPress 7.1/PHP 8.4.20で取得したものです。
| 条件 | 1ページ目ID | 2ページ目ID | found_posts | max_num_pages |
|---|---|---|---|---|
| 固定offset 2 | 55、56、57 | 55、56、57 | 8 | 3 |
| ページ別offset、総数未補正 | 55、56、57 | 58、59、60 | 8 | 3 |
| ページ別offset、総数補正 | 55、56、57 | 58、59、60 | 6 | 2 |
固定offsetの行は、1・2ページ目の3件がすべて同じです。次の行では投稿の重複が消えていますが、総ページ数は3のまま。最後の行で総件数も補正すると、必要な2ページだけになります。
補正後は6件・2ページになります。取得対象へ9件目の検証投稿(ID 61)を加えると、除外後の総件数は7、総ページ数は3となり、3ページ目にID 61だけが表示されます。今回はID昇順の最後へ対象を加えるため、1・2ページ目のIDは変わりません。新着順の先頭へ投稿が増える場合とは分けて確認します。
なお、取得結果が0件のクエリでは、WordPressが総件数の計算を省略する場合があります。空のページだけのfound_postsから、一覧全体の件数を判断しないでください。投稿が取得できた1・2ページ目の値と、実際に出力したページリンクも合わせて確認します。
offset付きページ送りでよくある間違い
修正を増やす前に、取得位置と総件数のどちらがまだ合っていないかを確認します。次の項目を上から見直すと、同じ原因を何度も調べずに済みます。
修正前に確認したいこと
- 固定したoffsetとpagedを一緒に渡していないか
- ページ別のoffsetだけを直し、found_postsを補正し忘れていないか
- found_postsからページごとのoffsetを引いていないか
- found_postsの補正がほかの一覧にもかかっていないか
- ページ送りが必要なのにno_found_rowsをtrueにしていないか
- 除外したい投稿IDが分かるのにoffsetを使っていないか
- 1ページ目と2ページ目の投稿IDを比べたか
- 投稿を追加したあとも最後のページを確認したか
offsetを外してもページ送りが動かない場合は、WP_Queryでページネーションが動かない原因もご覧ください。通常の設定から組み直す場合は、表示件数とページネーションの設定方法を確認してください。
offset・found_posts・ページ送りの公式資料
offsetとpagedの注意点や、総件数を補正するフィルターの仕様は、次のWordPress公式資料で確認できます。
WordPress公式
確認に使った公式ドキュメント
投稿が増減しても、最後までページを送れるか確認する
2ページ目が直っただけで終わらず、先頭から最後まで投稿IDを比べます。投稿を追加・削除したときにも、必要なページだけが残り、他の一覧の件数を変えていないか確かめてから反映しましょう。
本番へ反映する前に
- 検証環境で動作を確かめ、変更前のファイルへ戻せる
- 1ページ目から最終ページまで投稿の重複・欠落がない
- 投稿が増減しても総件数とページ番号が合い、余分な空ページへのリンクがない
- 補正対象ではない他の一覧の件数・表示は変わっていない
- 調査用ログを取り除き、スマホでもページ番号を操作できる
この記事の検証情報
検証環境
- WordPress
- 7.1.4
- PHP
- 8.3.36
- テーマ
- wpq-media + DDEV
- 対象
- カスタム投稿 / taxonomy / meta_query
更新履歴
- 2026年10月13日 初回公開
この記事を活用する
あとで見返す・共有する
お気に入りに保存できます。役に立ったポイントは、いいねや共有で教えてください。






