最初に投稿IDで重複している場所を分ける
タイトルではなく投稿IDを表示し、同じIDが「1つの一覧内」「別の表示枠」「別ページ」のどこで繰り返されているかを確認します。場所が分かると、直すコードを絞れます。
別の表示枠なら表示済みIDをpost__not_inへ渡します。同じ一覧ならテンプレートの二重出力や配列、表示だけ同じならwp_reset_postdata()、別ページならoffsetとpagedを順に確認します。
同じ記事がどこで重複しているか投稿IDで確認する
同じ投稿IDが1つの一覧内で複数回出ているのか、メイン一覧・関連記事・人気記事など別の枠に同じ記事が出ているのかを分けます。前者はループや配列、後者は除外IDと表示枠の役割を確認します。
| 見えている症状 | まず疑う原因 | 最初の確認 |
|---|---|---|
| 同じ一覧の中に同じ記事が2回出る | 手動配列、テンプレートの二重出力、ループ処理の重複 | 投稿IDを表示して同じIDが2回あるか見る |
| 上部の注目記事と通常一覧に同じ記事が出る | 複数クエリ間で除外していない | 先に表示したIDをpost__not_inに渡す |
| 最新記事一覧の先頭に固定記事が混ざる | 先頭固定表示の影響 | ignore_sticky_postsまたは固定投稿IDの除外を確認する |
| 次のブロックのタイトルやリンクが前の投稿になる | wp_reset_postdata()忘れ | サブループ後にwp_reset_postdata()があるか見る |
| ページをまたぐと同じ記事が出る | offsetとpagedの組み合わせ、条件変更の影響 | ページ番号ごとのID一覧を確認する |
最初に投稿IDで重複を確認する
タイトルだけを見ると、似たタイトルの記事や同じテンプレート表示を重複と見間違えることがあります。まずは、表示されている記事のIDを一時的に出して、同じ投稿IDが出ているのかを確認します。
確認用のコードは、公開画面に出したままにしない前提です。ローカル環境や検証環境で、少ない件数だけ表示して確認します。posts_per_page を3件程度にしておくと、どのIDがどの順番で出ているかを追いやすくなります。
次のコードは、通常投稿を3件だけ取得し、タイトルとIDを一緒に確認する最小例です。原因調査用なので、まずは条件を増やさずに小さく試します。
原因を切り分けるための一時的な確認コードです。ローカルなどの検証環境だけで使い、確認後は削除してください。
$args = [ 'post_type' => 'post', 'posts_per_page' => 3, 'ignore_sticky_posts' => true,]; $debug_query = new WP_Query( $args ); if ( $debug_query->have_posts() ) { while ( $debug_query->have_posts() ) { $debug_query->the_post(); echo esc_html( get_the_ID() . ' : ' . get_the_title() ); echo "\n"; }} wp_reset_postdata();このコードのポイントは、get_the_ID()を使って投稿IDを確認している点です。同じタイトルが並んでいてもIDが違うなら、実際には別記事です。逆に同じIDが同じ一覧の中で複数回出ているなら、ループや配列の扱いを重点的に見ます。
また、ignore_sticky_postsを一度trueにしている点も確認用として意味があります。先頭固定表示によって投稿が上へ移動して見えるだけなのかを切り分けるためです。最終的に固定記事をどう扱うかは、記事一覧の目的に合わせて決めます。
重複表示の原因を1つずつ確認する
投稿IDで重複の場所を特定できたら、複数ループ、固定表示、後処理、共通処理、ページ送り、テンプレート呼び出しの順に確認します。
原因1:複数ループで同じ記事を除外していない
特に多いのは、同じページ内に複数の一覧を置いているケースです。たとえば、上部に「注目記事」を1件表示し、その下に「最新記事」を並べる場合、何もしなければ同じ記事が両方に出ることがあります。
これはWP_Queryの不具合ではなく、2つのクエリがそれぞれ独立して投稿を取得しているためです。先に表示した記事を後続のクエリから除外したいなら、表示済みの投稿IDを配列にためて、次のクエリで post__not_in に渡します。
次のコードは、先に表示した記事IDを後続の一覧から除外する例です。トップページで「注目記事1件」と「最新記事5件」を分けるような場面を想定しています。
クラシックテーマのテンプレート(またはテンプレートパーツ)で、一覧を表示したい位置に置く例です。投稿本文やfunctions.phpへ、そのまま貼るコードではありません。
$shown_post_ids = []; $featured_query = new WP_Query( [ 'post_type' => 'post', 'posts_per_page' => 1, 'ignore_sticky_posts' => true,] ); if ( $featured_query->have_posts() ) { while ( $featured_query->have_posts() ) { $featured_query->the_post(); $shown_post_ids[] = get_the_ID(); echo esc_html( get_the_title() ); }} wp_reset_postdata(); $latest_query = new WP_Query( [ 'post_type' => 'post', 'posts_per_page' => 5, 'post__not_in' => $shown_post_ids, 'ignore_sticky_posts' => true,] ); if ( $latest_query->have_posts() ) { while ( $latest_query->have_posts() ) { $latest_query->the_post(); echo esc_html( get_the_title() ); }} wp_reset_postdata();このコードのポイントは、$shown_post_idsに先に表示した投稿IDを入れている点です。後続のWP_Queryでは、その配列をpost__not_inに渡すことで、同じ記事をもう一度表示しないようにしています。
公式リファレンスでも、post__not_in は取得しない投稿IDの配列として説明されています。ここで注意したいのは、カンマ区切りの文字列ではなく、整数IDの配列として渡すことです。
原因2:先頭固定表示の投稿が重複して見える
WordPressには、投稿を一覧の先頭へ移動する先頭固定表示の仕組みがあります。通常の一覧とは別に固定記事を上部へ出す実装と組み合わせると、同じ記事が上と下に出たように見えることがあります。
WP_Queryでは、先頭固定表示をどう扱うかをignore_sticky_postsやpost__not_inで調整できます。固定記事を完全に除外したいのか、先頭へ移動させず通常位置には出してよいのかで、使う指定が変わります。
| やりたいこと | 使う指定 | 考え方 |
|---|---|---|
| 固定記事を上に持ち上げたくない | ignore_sticky_posts | 固定記事を特別扱いしない |
| 固定記事を一覧から完全に除外したい | post__not_in に get_option( 'sticky_posts' ) を渡す | 固定記事IDを取得対象から外す |
| 固定記事だけを別枠で出したい | post__in に固定投稿IDを渡す | 固定記事用の一覧を別に作る |
ここで混同しやすいのは、ignore_sticky_postsは「固定記事を完全に消す」という意味ではないことです。
固定記事を一覧から完全に除外したい場合は、get_option( ‘sticky_posts’ )で取得した固定投稿IDをpost__not_inへ渡します。原因3:wp_reset_postdata()を忘れている
別ループ後の表示が乱れる理由は、wp_reset_postdata()が必要な理由で詳しく説明しています。重複が2ページ目以降だけで起きる場合は、ページネーションの確認順も続けて確認してください。
WP_Queryで the_post() を使うと、そのループ内ではテンプレートタグが現在の投稿を参照します。サブループが終わったあとに wp_reset_postdata() を呼ばないと、後続のタイトルやリンクが意図しない投稿を参照してしまうことがあります。
この場合、実際には同じ投稿がクエリで重複しているわけではなく、テンプレートタグが前のループの投稿情報を見ているだけ、ということがあります。表示上は同じタイトルやリンクが続くため、重複表示に見えやすいです。
公式のWP_Queryの標準ループ例でも、別クエリでthe_post()を使った後にwp_reset_postdata()が置かれています。この関数が戻すのはグローバルな投稿データであり、クエリ結果の重複そのものではありません。複数ループでは、取得・表示・復元をひとまとまりで確認してください。
原因4:pre_get_postsが意図しないクエリにも効いている
pre_get_postsはクエリ実行前に条件を変更するフックです。対象を限定しないと、管理画面、検索結果、サブクエリ、ウィジェット側のクエリまで変わることがあります。
件数、投稿タイプ、除外条件、並び順を変更している場合は、is_admin()と$query->is_main_query()で影響範囲を確認します。
次のコードは、メインクエリだけを対象にする基本形です。これはそのまま重複を直す万能コードではなく、影響範囲を限定するための考え方として見てください。
子テーマのfunctions.phpまたは機能プラグインに置く例です。対象画面と実行条件を確認し、検証環境で試してから本番へ反映してください。
add_action( 'pre_get_posts', function ( $query ) { if ( is_admin() || ! $query->is_main_query() ) { return; } if ( $query->is_home() ) { $query->set( 'posts_per_page', 10 ); }} );このコードは、最初にis_admin()と$query->is_main_query()を確認し、管理画面と通常のセカンダリWP_Queryを対象外にします。
重複表示が起きたときは、テンプレート内のWP_Queryだけでなく、functions.php やプラグイン側で pre_get_posts を使っていないかも確認します。画面に見えているコードだけが原因とは限りません。
原因5:offsetやページネーションでページ間に重複が出る
1ページ目と2ページ目に同じ記事が出る場合は、offset と paged の組み合わせを疑います。offset は取得開始位置をずらす指定ですが、ページネーションと組み合わせると、ページごとの計算が複雑になりやすいです。
たとえば、1ページ目だけ注目記事を別枠で出し、その下の一覧に offset を使っている場合、2ページ目以降で同じ記事が出たり、件数がずれたりすることがあります。表示件数が合わない問題とも近いですが、重複表示ではページごとの投稿IDを見比べるのが重要です。
ページ間重複を確認する順番
| 順番 | 確認すること |
|---|---|
| 1 | 1ページ目の投稿IDを控える |
| 2 | 2ページ目の投稿IDを控える |
| 3 | 同じIDがあるか確認する |
| 4 | offsetを一時的に外して挙動を見る |
| 5 | 注目記事の除外をpost__not_inで表現できないか検討する |
ページネーションがある一覧では、単に「上から数件飛ばす」よりも、先に表示したIDを除外する方が見通しが良い場合があります。特にトップページやアーカイブページで複数ブロックを組み合わせるなら、IDベースの除外を優先して考えると混乱しにくいです。
原因6:テンプレートやショートコードを二重に呼んでいる
クエリ条件が正しくても、同じテンプレートパーツやショートコードを2回呼び出していれば、当然同じ一覧が2回表示されます。これはWP_Queryの問題ではありませんが、見た目上は「同じ記事が重複した」と感じやすい原因です。
たとえば、トップページテンプレートで関連記事ブロックを読み込み、さらに本文内ショートコードでも同じ関連記事ブロックを呼んでいる場合、同じ一覧が2か所に出ます。ブロックエディター、テーマテンプレート、ウィジェット、ショートコードのどこで出しているかを分けて確認します。
テンプレートの二重呼び出しでは、投稿IDだけでなく表示枠も確認します。同じ枠の中で2回出るのか、別の枠で再取得しているのかを分けてください。
公式資料で確認したいポイント
重複表示の調査では、WP_Queryの各引数だけでなく、ループ後の状態やメインクエリの変更も関係します。特に post__not_in、ignore_sticky_posts、wp_reset_postdata()、pre_get_posts は、症状の切り分けでよく確認する項目です。
公式資料を読むときは、全部を一度に追う必要はありません。まずWP_Queryの標準ループとPost & Page Parametersを確認し、サブループ後の表示がおかしい場合は wp_reset_postdata()、メインクエリ変更の影響が疑われる場合は pre_get_posts を見ると進めやすいです。
公式リンク
公式ドキュメント
重複表示はID、ループ、除外条件の順に見る
同じ投稿IDが同じ一覧内に2回出るのか、別の表示枠で再取得されるのかで、直す場所が変わります。
複数ループがあるなら、先に表示したIDをpost__not_inで除外します。先頭固定表示が関係しているなら、固定記事を上に出したいのか、完全に除外したいのかを分けます。サブループ後に表示が崩れるなら、wp_reset_postdata()を確認します。ページ間で重複するなら、offsetとpagedの扱いを見直します。
重複表示の最終判断
- 重複の単位が同一ループ・複数ループ・ページ間のどれか特定した
- タイトルではなく投稿IDで重複を判定した
- 除外IDと先頭固定表示の扱いを要件として決めた
- offsetを使う場合のページ計算を確認した
- 後続のテンプレートタグが別投稿を参照していない
この記事の検証情報
検証環境
- WordPress
- 7.1
- PHP
- 8.3.33
- テーマ
- wpq-media + DDEV
- 対象
- カスタム投稿 / taxonomy / meta_query
更新履歴
- 2026年9月5日 初回公開
この記事を活用する
あとで見返す・共有する
お気に入りに保存できます。役に立ったポイントは、いいねや共有で教えてください。


