本文へ移動

トラブル解決

WordPressのページ送りで投稿が重複・欠落する原因
|offsetの使い方と計算方法

offsetは、取得結果の先頭から何件飛ばすかを指定する値です。ページ番号を表すpagedと固定offsetを一緒に使うと、2ページ目でも同じ投稿を取得することがあります。実際の一覧画面を見ながら、使う場面、確認する値、直し方を順に整理します。

Huddly Huddly 投稿日: 2026年10月13日 読了目安: 9分

まず除外したい投稿をIDで指定できるか確認する

おすすめ記事など、通常一覧から外したい投稿が分かっているなら、まずpost__not_inでそのIDを除外します。投稿の位置ではなく対象を指定できるため、投稿を追加しても同じ記事を除外しやすくなります。

「先頭から2件」のように位置で飛ばす必要がある場合だけoffsetを使います。その場合は、ページごとの取得開始位置と、ページ数の計算に使う総件数の両方を直します。

offsetは一覧の先頭から何件か飛ばしたいときに使う

たとえば、サイトの上部に「おすすめ記事」を2件表示し、その下に通常の記事一覧を置く画面を考えます。同じ2件を通常一覧へ重ねて出したくないため、一覧の先頭2件を飛ばしたくなることがあります。

offsetは「条件に合う投稿の先頭から何件飛ばして、取得を始めるか」を指定する値です。offset=2なら1件目と2件目を飛ばし、3件目から取得します。

この一覧にページ送りを付ける場合は、現在のページ番号を表すpagedも使います。先頭から飛ばす件数と、ページを移動したぶんの件数を、両方とも取得開始位置へ反映します。この例で使う3つの値を、先に整理しておきましょう。

値役割この例
offset先頭から飛ばす件数おすすめ記事ぶんの2件を飛ばす
posts_per_page1ページに表示する件数通常一覧は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へ、そのまま貼るコードではありません。

PHParchive.phpoffsetを固定したままpagedを併用する失敗例
$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ページ目
paged12
offset22
取得開始位置3件目3件目
表示した投稿記事C・記事D・記事E記事C・記事D・記事E

次の画像は、共通のサンプルサイトで2ページ目を開いた画面です。ページ送りは「2」になっていますが、通常一覧は1ページ目と同じID 55・56・57です。下部には、比較用に1ページ目の取得IDも添えています。

サンプルサイトの現在ページは2だが、通常一覧に1ページ目と同じID 55・56・57が表示された画面
固定offsetの失敗例ページ番号は変わっても3投稿が同じ

現在ページは2ですが、赤枠の投稿は1ページ目と同じ55・56・57です。offsetが2のままになっています。

この状態では、paginate_links()やパーマリンク設定を直しても取得結果は変わりません。まずWP_Queryへ最終的に入ったoffsetを確認します。

直す場所は取得位置と総件数の2つ

先頭から常に2件を除外し、その後を3件ずつ表示する場合は、2ページ目で3件ぶん先へ進む必要があります。ページごとの取得開始位置を次のように計算します。

値式例
基本の除外件数base_offset2
1ページの件数posts_per_page3
現在ページpaged1、2、3
実際のoffsetbase_offset + ( paged - 1 ) × posts_per_page2、5、8
ページ対象の総件数found_posts - base_offset8件なら6件
総ページ数除外後総件数 ÷ posts_per_pageを切り上げ6件なら2ページ

表のbase_offset、posts_per_page、pagedをPHPの変数へ置き換えると次のコードになります。先ほどのテンプレートパーツの取得処理と入れ替えます。この段階では取得位置だけを直します。総件数の補正は後の「最後の空ページ」の節で追加するため、まだページ送りの修正は完了していません。

クラシックテーマのテンプレート(またはテンプレートパーツ)で、一覧を表示したい位置に置く例です。投稿本文やfunctions.phpへ、そのまま貼るコードではありません。

PHPtemplate-parts/paginated-offset-list.phpページごとのoffsetを計算して独自WP_Queryへ渡す
$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
    先頭から飛ばす件数と1ページの表示件数を決める
  2. 2
    現在のページ番号を取得する
  3. 3
    ページ番号に合わせてoffsetを2、5、8と進める
  4. 4
    found_postsから先頭2件ぶんを引く
  5. 5
    補正後のmax_num_pagesでページリンクを作る

次は、同じサイトの2ページ目で取得位置だけを直した画面です。通常一覧がID 58・59・60へ変わっています。まだ総件数は補正していないため、ページ送りには「3」が残っています。

同じサイトの2ページ目でoffsetが5になり、通常一覧にID 58・59・60が表示された画面
取得位置を修正2ページ目は次の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へ、そのまま貼るコードではありません。

PHPtemplate-parts/latest-posts.php上部で使った既知の投稿IDをページ一覧から除外する
$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件です。

PHPfunctions.phpoffset付き独自クエリの総件数を補正する
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とループへ組み込んでください。

PHPpagination.php補正後の総ページ数でリンクを作る
$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フィルターも登録します。

PHPfunctions.phpnewsアーカイブのメインクエリで先頭2件を除外する
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_postsmax_num_pages3ページ目
補正なし830件の空ページ
先頭2件を補正62ページリンクを作らない
1件追加後に補正731件を表示
補正前後の実測表。found_postsは8から6、max_num_pagesは3から2、3ページ目は0件からリンクなしへ変化
総件数を修正空の3ページ目をページ送りから消す

これはサイト画面ではなく確認値の表です。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ページ目の値を並べると、どこまで直ったかを判断できます。

PHPtemporary-debug.phpoffset付きページネーションの取得位置と件数を確認する
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ページ目ID2ページ目IDfound_postsmax_num_pages
固定offset 255、56、5755、56、5783
ページ別offset、総数未補正55、56、5758、59、6083
ページ別offset、総数補正55、56、5758、59、6062

固定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ページ目から最終ページまで投稿の重複・欠落がない
  • 投稿が増減しても総件数とページ番号が合い、余分な空ページへのリンクがない
  • 補正対象ではない他の一覧の件数・表示は変わっていない
  • 調査用ログを取り除き、スマホでもページ番号を操作できる
WP Query BuilderBuilderで条件を組み直す条件を選び、WP_Queryのコードを画面で作れます。Builderでクエリを作る

この記事を書いた人

Huddly

Web制作 / フロントエンドエンジニア

WordPressとフロントエンド実装で迷いやすい点を、公式情報と検証結果を分けながら、初学者にも追いやすい順序で整理しています。

この記事の検証情報

検証環境

WordPress
7.1.4
PHP
8.3.36
テーマ
wpq-media + DDEV
対象
カスタム投稿 / taxonomy / meta_query

更新履歴

  • 2026年10月13日 初回公開

この記事を活用する

あとで見返す・共有する

お気に入りに保存できます。役に立ったポイントは、いいねや共有で教えてください。

任意のご支援 役に立ったときだけ、活動を応援できます OFUSEはサイト・記事制作に使います。支援の有無で、読める内容や使える機能は変わりません。