短答:多くはテンプレート(+リダイレクト系の処理)が原因です。
「プレビュー → カテゴリー一覧に飛ぶ」は、テーマやプラグインが template_redirect / pre_get_posts / リライトで「投稿詳細」を「カテゴリーURL」にリダイレクトしている時によく起きます。
まず切り分け(最短手順)
-
テーマを一時的に標準に
管理画面 → 外観 → テーマ → Twenty Twenty-Five などに切り替え → 投稿編集画面の「プレビュー」を再テスト。
→ 直ればテーマ側の処理が犯人。 -
プラグインを疑う(特にリダイレクト/SEO/キャッシュ)
- Redirection / Yoast/All in One SEO / 301系 / キャッシュ / セキュリティ系を順に停止。
- Health Check & Troubleshooting プラグインの“トラブルシューティングモード”で、管理者だけプラグイン無効+標準テーマで再現確認。
→ 直ればそのプラグインの設定 or 相性。
-
パーマリンクを再保存
設定 → パーマリンク → 何も変えず「変更を保存」でリライトルールを再生成。
テーマ側でよくある原因(チェックポイント)
-
template_redirectでの強制リダイレクト// 例:投稿を「主カテゴリのURL」へ301で飛ばす独自処理 add_action('template_redirect', function () { if (is_single() && !is_preview()) { // ← is_preview() ガードが無いとプレビューも飛ぶ // カテゴリURLへ wp_redirect(...); exit; } });➜
is_preview()やis_admin()を条件にプレビュー時はリダイレクトしないように修正。 -
pre_get_postsでクエリを書き換え
is_main_query()を見ずにカテゴリーアーカイブ相当に変更してしまうケース。
➜ 必ずif ( $q->is_main_query() && !is_admin() && !is_preview() )のようにガード。 -
single.php/single-post.phpが無い/テンプレ階層のミス
代替でarchive.phpが拾われ、結果的にカテゴリーっぽく見えることも。
➜single.php(またはsingle-post.php)の存在を確認。 -
カテゴリー優先の独自ルーティング
%category%/%postname%/構成に合わせて「常にカテゴリ付きURLへ正規化」するカスタムが入っている。
➜ プレビューURL(?p=ID&preview=true等)では正規化をスキップ。
プラグイン側でよくある原因
-
SEOプラグインの“正規URL(canonical)”や“プライマリカテゴリへリダイレクト”系設定
プレビューURLから正規化が働いてカテゴリに流されることがある。
➜ 正規化(canonical/リダイレクト)の設定をオフ、またはプレビュー時は無効に。 -
Redirection/301系ルール
投稿スラッグやカテゴリー名の衝突で包括的ルールにヒットしてしまう。
➜ 当該投稿のスラッグでヒットするルールがないか確認。除外条件にpreview=trueを追加。 -
キャッシュ/静的化
プレビューのクエリを落としてキャッシュ済みのカテゴリページを返している。
➜ ログインユーザー/プレビュー時はキャッシュしない設定に。
リライト&スラッグ衝突の落とし穴
-
カテゴリーと固定ページ/投稿のスラッグ衝突(例:どちらも
news)
ルールの優先でカテゴリへ解決され、プレビュー時にも巻き込まれる。
➜ どちらかのスラッグ・ベース(/category/等)を変更。 -
カテゴリーのベースを外している(
/category/なし)+複雑なカスタム
ルールが曖昧になり、プレビューURLの正規化でカテゴリに寄ることがある。
➜ いったんベースを戻すか、正規化処理をプレビュー除外。
最低限入れておく“プレビュー保護”サンプル
// リダイレクト・正規化・書換え系の共通ガード
function is_preview_context(): bool {
return (function_exists('is_preview') && is_preview())
|| (isset($_GET['preview']) && $_GET['preview'])
|| (isset($_GET['preview_id']));
}
// 例:template_redirect での保護
add_action('template_redirect', function () {
if (is_preview_context()) return; // ★プレビュー時は何もしない
// …ここに正規URLへのリダイレクト等…
});
// 例:pre_get_posts での保護
add_action('pre_get_posts', function ($q) {
if (! $q->is_main_query() || is_admin() || is_preview_context()) return;
// …ここにクエリ変更…
});
それでも直らない時に見るもの
-
実際のプレビューURL
例:/?p=123&preview=trueor/%postname%/?preview_id=123&preview_nonce=...
➜ これが別URLへ301/302されていないか(開発者ツールのネットワークで確認)。 -
WP_DEBUG_LOGでログを取る
wp-config.phpにてdefine('WP_DEBUG', true); define('WP_DEBUG_LOG', true); define('WP_DEBUG_DISPLAY', false);➜
wp-content/debug.logにヒントが出ることがあります。
まとめ
- 原因はテーマ/プラグインのリダイレクト・正規化・クエリ書換えが9割。
-
プレビュー時(
is_preview())は何もしないガードが最重要。 - 標準テーマ+プラグイン停止で再現しなければ、その差分(コード/設定)を見直してください。
必要なら、今のテーマの functions.php(該当箇所)やプレビューURLを貼ってください。こちらで“どこが飛ばしているか”をピンポイントで洗い出します。