追加した一覧ではthe_post()をwhileの直後、表示の前に置く
関連記事や新着記事の一覧では、have_posts()で次の投稿があるかを確かめ、the_post()で表示する投稿へ切り替えます。続くthe_title()やthe_content()は、その投稿のタイトルと本文を表示します。
new WP_Query()の結果を$list_queryへ入れた場合は、whileの直後に$list_query->the_post()を1回呼びます。投稿IDを関数へ渡してタイトルやURLだけを取得する実装では、the_post()を使わない方法も選べます。
関連記事の一覧をテンプレートタグで表示する準備
関連記事や新着記事の一覧を追加するコードでは、have_posts()とthe_post()が続けて出てきます。名前が似ていても、前者は次の投稿があるかの確認、後者は表示する投稿の切り替えです。
この記事では、同じA・B・Cの3記事を使い、the_post()の位置だけを変えて表示を比べます。順番に出れば正常、同じ記事の繰り返しや1件ずれがあれば呼び出し方を見直す、という例です。コードのID 61・62・63がA・B・C、ID 64がブラウザで開く元のページに当たります。自分の環境で試すときは、実在する投稿IDへ置き換えてください。
$list_queryは、取得結果を受け取るためにこの例で付けた変数名です。この結果をwhileで1件ずつ表示する処理を、ここでは「追加ループ」と呼びます。今回は関連記事を自動で選ぶ方法ではなく、同じ3件でthe_post()の呼び出し位置を比べます。
new WP_Query()の基本的な書き方を先に確認したい場合は、次の記事から読むとコードの流れがつながります。
new WP_Query()は、条件に合う投稿を$list_query->postsへ保存します。ただし、取得しただけでは、the_title()やthe_content()がどの投稿を表示するかまでは切り替わりません。
| 処理 | 役割 | この処理だけで変わるもの |
|---|---|---|
| have_posts() | 次に表示できる投稿があるか確認する | true / falseを返す。ループ位置は進めない |
| the_post() | 取得結果を次の1件へ進め、投稿データを準備する | current_postが進み、global $postとget_the_ID()がその投稿を使う |
| the_title() / the_content() | the_post()で準備した投稿を表示する | タイトルや本文を出力する。次の投稿へは進めない |
WordPress公式のWP_Query::the_post()は、次の投稿を取り出し、その投稿をループ内で使えるように準備します。the_post()自体がタイトルや本文を表示するわけではありません。なお、have_posts()は次の投稿へ進める関数ではありませんが、ループ終了時にはクエリ内の位置を巻き戻す処理も行います。
while ( $query->have_posts() )だけで回すと、ループ位置が進まないため同じtrue判定が続きます。ループ内で必ず次へ進む処理が必要です。正しい位置はwhileの直後・テンプレートタグの前
whileの中で、記事を表示する前にthe_post()を書きます。次はクラシックテーマの例で、子テーマのtemplate-part.phpにあるPHPの処理へ追加する部分です。新しくこのファイルを作る場合は、先頭に<?phpを付けてからコードを入れます。すでにPHPが始まっている場所へ追加するなら、開始タグは不要です。
個別記事へ表示する場合は、実際に使われるsingle.phpのメインループ内で、the_content()の後などへget_template_part( ‘template-part’ );を追加して呼び出します。パーツを保存するだけでは表示されないため、表示元からの呼び出しも必要です。下のコードは、元の記事のthe_post()が実行された後に動かしてください。
クラシックテーマのテンプレート(またはテンプレートパーツ)で、一覧を表示したい位置に置く例です。投稿本文やfunctions.phpへ、そのまま貼るコードではありません。
$list_query = new WP_Query( [ 'post_type' => 'post', 'post_status' => 'publish', 'posts_per_page' => 3, 'post__in' => [ 61, 62, 63 ], 'orderby' => 'post__in', 'ignore_sticky_posts' => true, ]); if ( $list_query->have_posts() ) { while ( $list_query->have_posts() ) { $list_query->the_post(); echo '<article>'; the_title( '<h2>', '</h2>' ); the_content(); echo '</article>'; }} else { echo '<p>表示できる投稿はありませんでした。</p>';} wp_reset_postdata();have_posts()がtrueになった直後に$list_query->the_post()を呼ぶため、その下のthe_title()とthe_content()は同じ投稿を参照します。1周につき1回だけ呼ぶと、取得順どおりに1件ずつ進みます。
次の画像は、共通のサンプルサイトで実際にWP_Queryを動かした画面です。ページ内の関連記事に、確認用記事A・B・Cが上から取得順に並んでいます。下部の確認値でも、current_postが0・1・2と進んでいます。

緑枠の関連記事はA・B・Cの順です。下部のcurrent_postも0→1→2と進みます。
一覧で本文全体が不要なら、the_content()を抜粋や任意の表示へ変更できます。表示内容を変えても、テンプレートタグを使う前に投稿データを準備する順番は同じです。
このコードは本番と分けたWordPress環境で、A・B・Cが順番に表示されるか試します。検証や公開に使うサーバーをこれから用意する場合は、Xserverの料金やバックアップ条件も確認できます。サーバーを変えるだけでthe_post()の位置が直るわけではありません。
WordPress標準の一覧と、追加した一覧では呼び出し方が変わる
関数として書くthe_post()は、WordPressがURLに合わせて用意したメインクエリを進めます。一方、$list_query->the_post()は、この例で追加した一覧を進めます。->は、左側の$list_queryに対して右側の処理を呼び出す書き方です。このようにオブジェクトを通じて呼ぶ関数を「メソッド」と呼びます。どの一覧を進めるかによって、次のように書き分けます。
| 表示する一覧 | 次の投稿を確かめる | 表示する投稿へ切り替える |
|---|---|---|
| WordPress標準のメイン一覧 | have_posts() | the_post() |
| $list_queryで追加した一覧 | $list_query->have_posts() | $list_query->the_post() |
追加ループの中で関数the_post()を呼ぶと、意図した$list_queryではなくメインクエリを進めます。追加した一覧では、have_posts()とthe_post()の左側を同じクエリ変数にそろえます。
the_post()を呼ばないと表示する投稿が切り替わらない
WP_Queryを作ると、取得結果は$list_query->postsへ入ります。一方、テンプレートタグが使うglobal $postは、$list_query->the_post()を呼ぶまで元の記事のままです。
次は仕組みを説明するための失敗例です。投稿があると終了しないため、そのまま実行しないでください。最初のコードで作った$list_queryを使っても、この形ではループを次へ進める処理がありません。
while ( $list_query->have_posts() ) { the_title(); the_content();}このコードは実行し続けないでください。投稿がある場合、have_posts()の判定がtrueのままになり、同じ処理を繰り返します。タイトルと本文も独自クエリの投稿へ切り替わらず、直前までglobal $postに入っていた記事を表示します。
画像の検証では無限ループを避けるため、3回で打ち切るfor文で同じ状態を確認しています。3回ともcurrent_postは-1のままで、画面には取得したA・B・Cではなく、元の確認ページが繰り返し表示されました。

赤枠の3件がすべて元のページID 64になっています。current_postは3回とも-1のままです。
早すぎると先頭を飛ばし、遅すぎると1件ずれる
the_post()は、呼ぶ位置が早すぎても遅すぎても表示がずれます。
| 置いた位置 | 起きること | 3件A・B・Cの表示 |
|---|---|---|
| whileの直後、表示前 | the_post()で準備した投稿をそのまま表示できる | A、B、C |
| whileより前にも1回呼ぶ | Aを表示前に消費する | B、C |
| the_title()などの後 | 表示時点では1つ前の投稿を使う | 元の記事、A、B。Cは準備後にループ終了 |
| 呼ばない | 位置が開始前から進まない | 元の記事を表示したまま判定が続く |
ループ前に先頭投稿だけ準備してから、ループ内でもthe_post()を呼ぶと、最初に表示するまでに2回進むため、Aを飛ばしてBから表示します。反対に、テンプレートタグの後で呼ぶと、表示と投稿データの準備が1周ずつずれます。
迷ったら「have_posts()で次の投稿を確認 → the_post()で表示対象を切り替える → テンプレートタグで表示」の順番になっているかを見直します。次の画面では、表示したあとにthe_post()を呼んでいます。1回目は元の記事、2回目は確認用記事A、3回目は確認用記事Bとなり、確認用記事Cは表示されません。

赤枠は元のページ→A→Bの順で、Cがありません。current_postは表示時点で-1→0→1です。
テンプレートタグはthe_post()で準備した投稿を使う
get_the_ID()は投稿IDを引数として受け取らず、the_post()で切り替えた投稿のIDを返します。the_title()はその投稿のタイトルを、the_content()はその投稿の本文をフィルターへ通して表示します。
そのため、取得したい投稿IDをget_the_ID()へ渡して表示対象を変えることはできません。追加ループでは先に$list_query->the_post()で表示する投稿を準備するか、IDや投稿オブジェクトを受け取れる取得関数を使います。
| 関数 | どの投稿を使うか | 主な用途 |
|---|---|---|
| get_the_ID() | the_post()で切り替えた投稿 | 追加ループ内の投稿IDを取得 |
| the_title() | the_post()で切り替えた投稿 | 追加ループ内のタイトルを表示 |
| the_content() | the_post()で切り替えた投稿 | 追加ループ内の本文をフィルター後に表示 |
| get_the_title( $post_id ) | 引数で指定したIDまたは投稿 | global $postを変えずにタイトルを取得 |
| get_permalink( $post_id ) | 引数で指定したIDまたは投稿 | global $postを変えずにURLを取得 |
タイトルやURLだけを扱う小さな処理では、IDを指定する方が分かりやすいことがあります。本文表示はthe_content()のフィルター処理やループの状態も関係するため、通常の一覧ではhave_posts()、the_post()、テンプレートタグの順に書く方法が分かりやすいです。
the_post()を使わない方が単純なケース
取得した投稿IDを別処理へ渡すだけ、タイトルとURLをID指定で組み立てるだけ、件数を確認するだけなら、global $postを切り替えずに処理できます。
クラシックテーマのテンプレート(またはテンプレートパーツ)で、一覧を表示したい位置に置く例です。投稿本文やfunctions.phpへ、そのまま貼るコードではありません。
$id_query = new WP_Query( [ 'post_type' => 'post', 'post_status' => 'publish', 'posts_per_page' => 3, 'fields' => 'ids', 'post__in' => [ 61, 62, 63 ], 'orderby' => 'post__in', 'ignore_sticky_posts' => true, ]); if ( $id_query->posts ) { echo '<ul>'; foreach ( $id_query->posts as $post_id ) { printf( '<li><a href="%1$s">%2$s</a></li>', esc_url( get_permalink( $post_id ) ), esc_html( get_the_title( $post_id ) ) ); } echo '</ul>';}この例は$id_query->the_post()やsetup_postdata()を呼ばないため、global $postを切り替えません。そのため、この処理だけを理由にwp_reset_postdata()を呼ぶ必要もありません。
ただし、the_title()、the_content()、投稿IDを省略したget_permalink()など、ループで切り替えた投稿を使うテンプレートタグへ戻す場合は、基本のループ構造を使う方が分かりやすいです。
投稿IDを使う方法と通常のループの違いを確認できたら、次は投稿タイプや件数をご自身の一覧に合わせます。WP Query Builderで取得条件を作り、表示する処理はこの記事の例と組み合わせます。
一覧の取得条件を組み立てる
投稿タイプ・絞り込み・件数を整理し、生成した$argsをループへ渡します。the_post()を使う場合は、呼び出し位置と最後のリセットも確認してください。
追加ループを使ったあとはwp_reset_postdata()で戻す
追加クエリのthe_post()は、global $postをループ中の各投稿へ切り替えます。ループが終わった直後にwp_reset_postdata()を置き、その後のテンプレートタグが、ブラウザで開いている記事を再び参照できる状態へ戻します。
リセットが必要かは、WP_Queryを作ったかではなく、the_post()やsetup_postdata()で投稿データを切り替えたかで判断します。戻し忘れで別タイトルが出る症状や、リセット関数の違いを詳しく知りたい場合は、次の記事もあわせてご覧ください。
同じ3件で比べると、呼び出し位置による違いが分かる
3枚の画面の違いを、ループの位置でも比べてみましょう。表のcurrent_postは表示する時点の値で、-1は開始前、0・1・2はA・B・Cに当たります。終了後はhave_posts()によって巻き戻されるため、ループの外で記録した値とは区別してください。値は上の画像と同じ、ローカルのWordPress 7.1/PHP 8.4.20で取得しています。
| 条件 | 表示時のcurrent_post | global $post / get_the_ID() | 表示対象 |
|---|---|---|---|
| 開始前 | -1 | 64 / 64 | 元の確認ページ |
| while直後にthe_post() | 0、1、2 | 61、62、63で一致 | 確認用記事A、B、C |
| the_post()なしで3回確認 | -1のまま | 3回とも64 | 3回とも元の確認ページ |
| while前にも1回呼ぶ | 1、2 | 62、63 | 確認用記事B、C |
| 表示後に呼ぶ | -1、0、1 | 64、61、62 | 元の確認ページ、確認用記事A、B。Cは未表示 |
投稿オブジェクトをget_the_title( $item )やget_the_content( null, false, $item )へ渡す場合は、対象の投稿を明示して値を取得できます。投稿を指定して値を取る方法と、テンプレートタグ用の表示対象を切り替える方法は分けて考えます。
なお、get_the_content()は投稿を指定できますが、それだけではthe_content()と同じフィルター処理を行いません。公開画面で本文を通常どおり表示する場合は、ループ内でthe_post()の後にthe_content()を使う形を基本にします。
表示がずれるときは、ループ位置と投稿IDを比べる
タイトルだけを見比べると、同名投稿や1件遅れを見落とすことがあります。最初のコードを置いたテンプレートパーツで、$list_query->the_post()の直後に次のログを一時的に追加します。ループ位置と3種類の投稿IDに加え、タイトルも記録します。
global $post; error_log( wp_json_encode( [ 'current_post' => $list_query->current_post, 'query_post_id' => $list_query->post instanceof WP_Post ? $list_query->post->ID : null, 'global_post_id'=> $post instanceof WP_Post ? $post->ID : null, 'get_the_ID' => get_the_ID(), 'title' => get_the_title(), ], JSON_UNESCAPED_UNICODE ));wp-config.phpでWP_DEBUGとWP_DEBUG_LOGを有効にしている場合、通常はWordPressのwp-content/debug.logで確認できます。画面にログを出さないため、WP_DEBUG_DISPLAYはfalseにします。確認用のログはローカルやステージング環境だけで使い、原因が分かったら削除してください。
正常なら、query_post_id、global_post_id、get_the_ID()が各周で同じIDになり、current_postが0から1ずつ増えます。
have_posts()とthe_post()の公式資料
have_posts()が確認する内容、the_post()が投稿データを準備する順序、テンプレートタグの参照先とループ後の戻し方は、次の公式資料で確認できます。
WordPress公式
確認に使ったWordPress公式ドキュメント
- WP_Queryのthe_post()次の投稿への移動と投稿データのセットアップを確認したい時
- WP_Queryのhave_posts()投稿の有無確認とループ終了時の処理を確認したい時
- WP_Query基本のループ、global $post、リセット位置をまとめて確認したい時
- the_post()メインクエリ用関数がglobal $wp_queryを呼ぶ実装を確認したい時
- get_the_ID()ループで準備された投稿のIDを取得する仕様を確認したい時
- the_title()ループで準備された投稿のタイトルを表示する仕様を確認したい時
- the_content()ループで準備された投稿の本文とthe_contentフィルターを確認したい時
- The Loophave_posts()、the_post()、テンプレートタグの基本の順番を確認したい時
- wp_reset_postdata()追加ループ後に、ブラウザで開いている記事へ投稿データを戻す方法を確認したい時
- WordPressのデバッグWP_DEBUG_LOGとdebug.logを使った確認方法を調べたい時
取得した記事を順番に表示できたか確かめる
最初の記事から最後の記事まで、抜けや繰り返しなく表示されているかを見ます。一覧の後にあるタイトルや本文も元の記事のままなら、最後のリセットまで確認できています。
the_post()の最終確認
- have_posts()とthe_post()の役割を分けた
- 追加した一覧では同じクエリ変数のthe_post()を使った
- while直後・テンプレートタグ前に1回だけ置いた
- global $post、get_the_ID()、クエリの投稿IDが各周で一致した
- 追加ループ後にwp_reset_postdata()を書いた
この記事の検証情報
検証環境
- WordPress
- 7.1.4
- PHP
- 8.3.36
- テーマ
- wpq-media + DDEV
- 対象
- カスタム投稿 / taxonomy / meta_query
更新履歴
- 2026年10月11日 初回公開
この記事を活用する
あとで見返す・共有する
お気に入りに保存できます。役に立ったポイントは、いいねや共有で教えてください。






