WordPress

WordPressのカテゴリを親子変更したら古いURLでも表示された原因と301転送

公開 更新 約5分で読めます

TL;DRこの記事の要点

  • カテゴリの親子を変えても、古いURLが200で表示され続けた
  • WordPressは階層URLを最後のスラッグだけで検索している
  • 今のURLと違えば301で転送する処理をテーマに追加した
目次5項目
  1. カテゴリの親子を変えると、URLも変わる
  2. カテゴリの階層URLは、最後のスラッグで検索される
  3. 同じページが2つのURLで見えるのは困る
  4. 今のURLと違えば301で転送する
  5. カテゴリの親子を変えるときの確認

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件ずつ登録する方法もあります。

カテゴリの親子を変えるときの確認

  1. 変更前のカテゴリURLを控えておく
  2. 変更後、古いURLを開いてステータスを見る(200のままなら重複している)
  3. 古いURLを今のURLへ301で転送する
  4. ページ送り(/page/2/)の古いURLも転送されるか見る

カテゴリの整理はリニューアルのついでにやりがちです。
見た目では気づきにくいので、親子を変えたら古いURLを一度開いておくと安心です。

この記事が参考になったら、応援クリックをお願いします

Cat's Handこの記事を書いた人

Web制作歴10年以上。WordPressのテーマカスタマイズやコーディングを中心に、副業・本業ともにweb制作に携わっています。このブログでは、実際に手を動かして分かったことを書いています。