WordPressでカテゴリの親子を変えたら、古いURLでもカテゴリページがそのまま表示されていました。
エラーにも転送にもならず、同じページが2つのURLで見える状態です。
原因は、WordPressが階層のあるカテゴリのURLを最後のスラッグだけで判定していることでした。
🎥 この記事の内容は動画でも解説しています。
きっかけは、このブログのリニューアルです。
「制作ツール」の子カテゴリだった「AI活用」を、親カテゴリに上げました。
移行の検証として、Claude Codeに古いURLも確認してもらったところ、この状態が見つかりました。
カテゴリの親子を変えると、URLも変わる
WordPressのカテゴリURLは、親子関係をそのまま含みます。
このブログでは、変更の前と後でこうなりました。
- 変更前(子カテゴリ):
/category/tools/tools-ai/ - 変更後(親カテゴリ):
/category/tools-ai/
古いURLは404になるか、新しいURLに転送されるものだと思いがちです。
ところが、古い/category/tools/tools-ai/を開いても、ステータス200でAI活用の一覧が表示されていました。
カテゴリの階層URLは、最後のスラッグで検索される
理由はWordPress本体のWP_Queryにありました。
カテゴリのURLは、内部ではcategory_nameという値(例: tools/tools-ai)として渡されます。WP_Queryはこの値からwp_basename()で最後の部分だけを取り出して、そのスラッグのカテゴリを探します。
つまりtools/tools-aiも、tools-aiだけで検索されます。
途中のtoolsが今の親と一致するかは、この処理では確かめていません。
本当にそうなのか、Claude Codeにローカル環境で試してもらいました。category_nameにいろいろな値を渡して、どのカテゴリとして扱われるかを見た結果です。
tools-ai→ AI活用(記事20件)tools/tools-ai→ AI活用(記事20件)nyan/tools-ai→ AI活用(記事20件)tools/nyan→ 該当なし
nyanというカテゴリは存在しません。
それでも最後がtools-aiなら、AI活用のページとして表示されました。
逆に最後のスラッグが存在しなければ、途中が正しくても見つかりません。
同じページが2つのURLで見えるのは困る
表示されるなら問題ないようにも見えます。
ただ、同じ一覧が/category/tools-ai/と/category/tools/tools-ai/の両方で見えると、検索エンジンにはどちらが正しいURLか伝わりにくくなります。
古いURLのリンクやブックマークも、新しいURLに寄っていきません。
正しいURLを伝える方法にはrel="canonical"もあります。
ただ、WordPress本体がcanonicalを出すのは個別の投稿や固定ページだけで、カテゴリ一覧には出しません。
このブログはテーマ側でカテゴリ一覧にも今のURLのcanonicalを出していますが、それでも古いURLは200のまま開けてしまいます。
カテゴリの親子を変えたら、古いURLは今のURLへ301で転送しておくのが安全です。
今のURLと違えば301で転送する
Claude Codeは、テーマのfunctions.phpから読み込んでいるファイルに、次の処理を追加しました。
カテゴリページを開いたとき、アクセスされたURLと、今のカテゴリURLを比べます。
違っていれば、今のURLへ301で転送します。
add_action( 'template_redirect', function () {
if ( ! is_category() ) {
return;
}
$request_uri = isset( $_SERVER['REQUEST_URI'] ) ? $_SERVER['REQUEST_URI'] : '';
$term = get_queried_object();
$current = untrailingslashit( preg_replace( '#/page/d+/?$#', '', (string) wp_parse_url( $request_uri, PHP_URL_PATH ) ) );
$expected = untrailingslashit( (string) wp_parse_url( get_category_link( $term ), PHP_URL_PATH ) );
if ( rawurldecode( $current ) !== rawurldecode( $expected ) ) {
$url = get_category_link( $term );
$paged = (int) get_query_var( 'paged' );
if ( $paged > 1 ) {
$url = trailingslashit( $url ) . 'page/' . $paged . '/';
}
wp_safe_redirect( $url, 301 );
exit;
}
} );
この処理で気をつけているのは3つです。
- 古いURLを1つずつ書かない。今後ほかのカテゴリの親子を変えても、同じ処理で転送される
/page/2/のようなページ送りも、ページ番号を残したまま転送する- 日本語スラッグのようなパーセントエンコードされたURLでも、デコードしてから比べる
追加したあと、本番で確かめた結果です。
/category/tools-ai/→ 200/category/tools/tools-ai/→ 301で/category/tools-ai/へ/category/nyan/tools-ai/→ 301で/category/tools-ai/へ/category/tools/tools-ai/page/2/→ 301で/category/tools-ai/page/2/へ
存在しない親を付けたURLも、今のURLにまとまりました。
コードを使うときは、子テーマかオリジナルテーマに入れてください。
親テーマに直接書くと、テーマの更新で消えてしまいます。
コードを書きたくない場合は、Redirectionのような転送用プラグインで、古いURLを1件ずつ登録する方法もあります。
カテゴリの親子を変えるときの確認
- 変更前のカテゴリURLを控えておく
- 変更後、古いURLを開いてステータスを見る(200のままなら重複している)
- 古いURLを今のURLへ301で転送する
- ページ送り(
/page/2/)の古いURLも転送されるか見る
カテゴリの整理はリニューアルのついでにやりがちです。
見た目では気づきにくいので、親子を変えたら古いURLを一度開いておくと安心です。