別ループ後にタイトルやURLがずれるのを防ぐ
記事下に新着一覧を置いたあと、タイトル、URL、アイキャッチが別の記事へ変わってしまったことはありませんか? クエリ条件が正しいのに表示だけがずれるなら、別ループ後の投稿データが残っている可能性があります。
ここでいうサブループは、URLに応じてWordPressが用意したメインループとは別に、new WP_Query()で作る追加の一覧です。wp_reset_postdata()は、そのサブループで切り替わったグローバル$postを、メインクエリの現在の投稿へ戻します。
wp_reset_postdata()で戻すのはglobal $post
global $postは、WordPressが「現在の記事」として扱っている投稿オブジェクトです。the_title()、the_permalink()、the_excerpt()など、引数を省略したテンプレートタグは、このグローバル変数を参照します。
PHPでは、関数の中に同じ名前のローカル変数$postを作ることもできます。関数内でglobal $post;と書いた場合は、ローカル変数ではなくWordPressのグローバル$postを使う、という意味です。別のWP_Queryで$query->the_post()を呼ぶと、この参照先がサブループの投稿へ切り替わります。
wp_reset_postdata()は、WP_Queryで切り替わったテンプレートタグの参照先を、メインクエリの現在の投稿へ戻すための後片付けです。任意の「呼び出し前の状態」へ戻す関数ではありません。必要になるのはどんなときか
必要になるのは、メインの記事を表示しながら、そのほかの記事一覧も出すときです。たとえば、記事下の関連記事、サイドバーの新着、トップページ内のおすすめなど、メインループとは別のWP_Queryでthe_post()を使う場面が当てはまります。
別ループを含むページ全体の流れが分からない場合は、先にメインクエリとサブループの違いを確認すると理解しやすくなります。コードの基本形も見直したい場合は、WP_Queryの基本構文を合わせて確認してください。
判断の中心は、WP_Queryを作ったかではなく、the_post()やsetup_postdata()で投稿データを切り替えたかです。
サイドバーだけで別ループを使う場合も、the_post()を呼んだならサイドバーのテンプレートパーツ内でリセットします。画面上で後続の表示がなくても、呼び出し元のテンプレートや別のパーツが後からテンプレートタグを使う可能性があるためです。wp_reset_postdata()は、別ループを持つテンプレートパーツや関数の中で、ループが終わった直後に置きます。
最小例で見るwp_reset_postdata()の位置
次は、個別記事の下に現在の記事を除いた新着3件を表示し、元の記事へ戻ったことまで確かめる例です。
クラシックテーマのテンプレート(またはテンプレートパーツ)で、一覧を表示したい位置に置く例です。投稿本文やfunctions.phpへ、そのまま貼るコードではありません。
$current_post_id = get_queried_object_id(); $args = [ 'post_type' => 'post', 'post_status' => 'publish', 'posts_per_page' => 3, 'post__not_in' => [ $current_post_id ], 'ignore_sticky_posts' => true,]; $latest_posts = new WP_Query( $args ); if ( $latest_posts->have_posts() ) { echo '<ul>'; while ( $latest_posts->have_posts() ) { $latest_posts->the_post(); echo '<li>'; echo '<a href="' . esc_url( get_permalink() ) . '">'; echo esc_html( get_the_title() ); echo '</a>'; echo '</li>'; } echo '</ul>';} else { echo '<p>表示できる記事はありませんでした。</p>';} wp_reset_postdata(); printf( '<p>戻り先: <a href="%1$s">%2$s</a></p>', esc_url( get_permalink() ), esc_html( get_the_title() ));get_queried_object_id()で現在の記事を除外し、ループ後にwp_reset_postdata()を呼びます。最後の「戻り先」に元記事のタイトルとURLが出れば、参照先が戻ったことを確認できます。
書かないと何が起きるのか
書き忘れてもエラーにならず、後続のタイトル、URL、ID、アイキャッチだけがサブループの最後の記事へずれることがあります。たとえば、関連記事のループ後にthe_title()を使うと、ページ本体ではなく最後の関連記事のタイトルが出ることがあります。get_permalink()を引数なしで呼べば、ボタンやパンくずのリンク先がその関連記事へ変わる可能性があります。get_the_ID()を使う条件分岐やカスタムフィールド、アイキャッチの取得も、別の記事IDを基準に動くことがあります。
この問題が見つけにくいのは、PHPエラーではなく「値だけがずれる」ためです。クエリの取得条件を直しても解消しないので、表示がおかしくなった場所の直前にサブループがないか、そのループ直後にwp_reset_postdata()があるかを確認します。
書き忘れを疑う順番
- 1カスタムWP_Queryの中でthe_post()を使っているか確認する
- 2そのループの後にwp_reset_postdata()があるか確認する
- 3ループ後にthe_title()やget_the_ID()など投稿依存のタグを使っていないか見る
- 4表示がずれている場所の直前に別ループがないか確認する
取得件数や重複も同時に気になる場合は、投稿データの戻し忘れとクエリ条件を分けて調べます。wp_reset_postdata()はタイトルやURLの参照先を戻しますが、WP_Queryが取得する件数は変えません。
wp_reset_query()との違い
2つの関数は、戻す対象が違います。追加一覧を作る通常の実装ではnew WP_Query()とwp_reset_postdata()を使います。wp_reset_query()は、非推奨のquery_posts()が置き換えたグローバル$wp_queryを戻し、その後で投稿データも戻す関数です。
| 使った方法 | ループ後の対応 | 判断理由 |
|---|---|---|
| new WP_Query()でthe_post()を使う | wp_reset_postdata() | メインクエリは置き換えず、投稿データだけ切り替わるため |
| get_posts()でsetup_postdata()を使う | wp_reset_postdata() | テンプレートタグ用の投稿データを切り替えるため |
| get_posts()でIDだけを使う | 通常は不要 | グローバル$postを切り替えないため |
| query_posts()を使う | wp_reset_query() | グローバル$wp_queryを置き換えるため。ただしquery_posts()自体を新しい実装では使わない |
既存コードにquery_posts()がある場合は、リセット関数だけを選び直すのではなく、query_postsを使わないほうがいい理由を確認し、pre_get_postsまたは別のWP_Queryへ置き換えられないか検討してください。
よくある勘違い
WP_Queryを作っただけで必ず必要になるわけではありません。the_post()やsetup_postdata()を使ったかで判断します。
| 勘違い | 実際の考え方 |
|---|---|
| WP_Queryを作っただけで必ず必要 | the_post()などで投稿データを切り替えたときに必要になりやすい |
| 最後に1回書けば十分 | 複数の別ループがあるなら、それぞれの後に戻す方が読みやすい |
| wp_reset_query()でも同じ | 戻している対象が違うため、new WP_Query()の後はwp_reset_postdata()が基本 |
| 表示がずれたらWP_Query条件が間違い | 条件ではなく、投稿データの戻し忘れが原因のこともある |
細かい例外を先に覚えるより、まずは「the_post()を使う別ループが終わったら戻す」と考えるのが実務的です。ただし、別ループを入れ子にした場合、内側でwp_reset_postdata()を呼んでも外側のサブループへ戻るとは限りません。入れ子を避けるか、外側の投稿を保存してsetup_postdata()で明示的に再設定します。
setup_postdata()を使った場合も元へ戻す
get_posts()で投稿オブジェクトを取得し、テンプレートタグを使いたい場合は、グローバル$postへ各投稿を代入してsetup_postdata()を呼びます。この場合も最後にwp_reset_postdata()が必要です。
クラシックテーマのテンプレート(またはテンプレートパーツ)で、一覧を表示したい位置に置く例です。投稿本文やfunctions.phpへ、そのまま貼るコードではありません。
global $post; $items = get_posts( [ 'post_type' => 'post', 'post_status' => 'publish', 'posts_per_page' => 3,] ); if ( $items ) { echo '<ul>'; foreach ( $items as $post ) { setup_postdata( $post ); printf( '<li><a href="%1$s">%2$s</a></li>', esc_url( get_permalink() ), esc_html( get_the_title() ) ); } echo '</ul>';} else { echo '<p>表示できる投稿はありませんでした。</p>';} wp_reset_postdata();IDだけを取得し、get_the_title( $id )やget_permalink( $id )のようにIDを渡せる関数だけで処理するなら、グローバル$postを切り替えないため、通常はリセット不要です。
公式資料で確認したいポイント
復元先はwp_reset_postdata()、標準ループはWP_QueryとThe Loop、似た関数との差はwp_reset_query()の公式資料で確認できます。
公式リンク
公式ドキュメント
投稿データ復元の最終判断
- 別ループが必要か、ID指定の取得で済むか判断した
- setup_postdata()またはthe_post()を使った箇所だけを復元対象にした
- 複数ループごとに復元位置を決めた
- wp_reset_query()との違いを混同していない
- 元記事のタイトルとURLが保たれることを画面で確認した
この記事の検証情報
検証環境
- WordPress
- 7.1
- PHP
- 8.3.33
- テーマ
- wpq-media + DDEV
- 対象
- カスタム投稿 / taxonomy / meta_query
更新履歴
- 2026年8月28日 初回公開
この記事を活用する
あとで見返す・共有する
お気に入りに保存できます。役に立ったポイントは、いいねや共有で教えてください。




