IT・個人事業

旧ブログの日本語URLからの301転送が、87本すべて効いていなかった話|Apacheはデコード後のパスで照合する

公開:

※ 本記事にはアフィリエイト広告(Amazonアソシエイト・楽天アフィリエイト等)が含まれています。

このブログは2026年7月末にWordPressから静的サイト(Astro)へ移行しました。移行の記事では「旧URLは全部301で新URLへ転送した」と書いています。嘘ではありません。.htaccess に87本、きちんと書いてありました。

ところが移行から3週間後、Search Consoleを見ていて血の気が引きました。検索から来た人の87%が、旧URLに着地して404を見ていたのです。転送は1本も動いていませんでした。

書いてあるのに、動いていない

旧ブログの記事URLは日本語スラッグでした。/在外公館-派遣員-仕事/ のような形です。ブラウザのアドレスバーやSearch Consoleでは、これがパーセントエンコードされて /%e5%9c%a8%e5%a4%96... と表示されます。

私はSearch Consoleに出ている表記をそのまま .htaccess に貼りました。

Redirect 301 /%e5%9c%a8%e5%a4%96%e5%85%ac%e9%a4%a8-%e6%b4%be%e9%81%a3%e5%93%a1-%e4%bb%95%e4%ba%8b/ /blog/gaimushou.../

これが87本。書き終えてデプロイして、移行完了のつもりでいました。確認は「ブラウザで旧URLを開いたら新URLに飛んだ」の1回だけ。そのとき開いたのが、たまたまASCIIスラッグの記事だったのが運の尽きでした。

原因:Apacheはデコードしてから照合する

Search Consoleの表示回数が旧URLに集中していることに気づいて、ターミナルから直接確かめました。

curl -sSI "https://vynsen.net/在外公館-派遣員-仕事/"

返ってきたのは HTTP/2 404。転送どころか、何もマッチしていません。一方で、ASCIIスラッグの旧URLはちゃんと 301 が返ります。

理由はこうです。Apacheの Redirect や RedirectMatch(mod_alias)は、リクエストされたパスをパーセントデコードしたあとでパターンと照合します。つまり実際に比較されるのは /在外公館-派遣員-仕事/ という生のUTF-8文字列です。そこに /%e5%9c%a8... というパターンを当てても、文字列として一致するはずがありません。

Apacheはデコード後のパスで照合する ブラウザが送るURL /%e5%9c%a8%e5%a4%96.../ Apacheがデコード /在外公館-派遣員-仕事/ .htaccess のパターンと照合 Redirect 301 /%e5%9c%a8.../ 一致しない → 404 私が3週間やっていたこと Redirect 301 /在外公館-.../ 一致する → 301 生のUTF-8で書く 見た目の表記ではなく、サーバーが照合する形で書く
ブラウザに見えている表記と、サーバーが見ている文字列は違う

直し方:生のUTF-8で書く

対処そのものは単純で、.htaccess に日本語をそのまま書くだけです。

Redirect 301 /在外公館-派遣員-仕事/ /blog/gaimushoutokakawarushigototoha-zaigaikoukanhakeninnoshikenta/

87本を書き換えて、今度は curl -sSI で確認しました。日本語スラッグの旧URLから 301 と location: ヘッダーが返るのを、1本ずつ目で見ています。

ファイルの文字コードはUTF-8(BOMなし)で保存してください。Windowsのメモ帳などで保存すると文字コードが変わることがあり、そうなると今度は別の理由でマッチしなくなります。

二つ目の罠:転送「先」は逆にエンコードしてはいけない

直したつもりで、もうひとつ踏みました。

旧ブログのタグページ(/tags/腸活/ など)も新サイトのタグページへ転送したかったので、転送先を /tags/%E8%85%B8%E6%B4%BB/ とエンコードして書きました。すると location ヘッダーがこうなります。

location: /tags/%25E8%2585%25B8%25E6%25B4%25BB/

% が %25 に化けています。Apacheは転送先のURLを組み立てるとき、% を「エスケープすべき文字」としてもう一度エンコードするのです。結果、存在しないURLへ飛んで404。

転送先も生のUTF-8で書くのが正解でした。照合パターン側も転送先側も、「ブラウザに見えている表記」ではなく「生の文字列」で書く。これが一貫したルールです。

検索流入はどう変わったか

直したのが8月13日。その後のSearch Consoleの推移です。

時点 新URLへのクリック 旧URLへのクリック
8月25日(過去3か月) 17 67
9月2日(過去3か月) 32 60

新URLが1週間で2倍、旧URLは減り始めました。Googleは正しい301を見つけると、評価を少しずつ新URLへ移していきます。3か月単位の集計なので数字の入れ替わりは緩やかですが、方向は明らかに変わりました。

9月に入ってからは1日あたりのクリックが7〜8月の約2倍になっています。これが全部リダイレクト修正の効果とは言い切れませんが、3週間も404を返していた期間と比べれば、少なくとも足を引っ張るものはなくなりました。

教訓:「書いてある」と「効いている」は別

今回の反省は3つです。

設定ファイルに書いてあることと、サーバーが実際にやっていることは別物。書き終えた時点では何も終わっていません。

確認はブラウザではなくcurlで。ブラウザは勝手にエンコードもデコードもするので、何が送られて何が返っているかが見えません。curl -sSI なら、ステータスコードと location ヘッダーがそのまま出ます。

サンプルは1本ではなく、種類ごとに。私はASCIIスラッグを1本見て安心しました。日本語スラッグ、タグページ、wwwありなし。種類が違えば別々に確認しないと意味がありません。

移行作業の全体像はWordPressをやめて静的サイトに移行した全記録に、移行後にLighthouseの点が落ちた話はLighthouse満点は「取る」より「保つ」が難しいに書いています。静的サイト移行は、移行した日より、そのあと1か月のほうがやることが多いです。