パーセントエンコーディングが実際に解決している問題
URLは本来、ブラウザやサーバー、メールクライアントなど数え切れないシステムの間を、確実に、コンパクトなテキストとして行き来できることを目的としており、その多くは歴史的に限られたASCII文字しか安全に扱えませんでした。パーセントエンコーディングは、その安全な範囲の外にある文字と、データとして使われている予約文字を、その文字のUTF-8バイト表現にもとづいた標準的な%XX形式に変換することで、どのシステムが扱っても曖昧さのない、安全にやり取りできる文字列であることを保証しています。
エンコードされたURLを実際に壊してしまうミス
二重エンコード、つまりすでにパーセントエンコードされた文字列にもう一度パーセントエンコードをかけてしまうミスは、実際によくある不具合のひとつです。既存の%記号が%25に変換されてしまい、見た目だけでは気づきにくい形でURLが壊れてしまいます。もうひとつよくある混同は、+と%20をどこでも同じ意味だと思い込むことです。+が「スペース」を意味するのはフォームエンコードされたクエリデータの中だけであり、URLのパス部分などで使うと、スペースではなく文字どおりのプラス記号として誤解されることがあります。
実装するときの基本方針
URLを手作業で文字列連結して組み立てるより、URLSearchParamsやURLクラス、各言語のURL組み立て用ライブラリを使うほうが安全です。変換するのは、値を組み立てる最後のタイミングで一度だけにします。受け取る側では、フレームワークがすでにデコードしている場合が多いので、自分でもう一度デコードしていないかを確認してください。なお、URLSearchParamsはスペースを + に変換する点で %20 を使う関数とは結果が異なります。
トラブルの切り分け方
リンクが開けない、検索キーワードが途中で切れる、といった場合は、まずURLに %25 が混ざっていないかを確認します。%25 は % 自体を変換した結果なので、二重エンコードのサインです。次に、&や#を含む値が変換されずに入っていないかを確認します。&は次のパラメータの始まりとして、#はそこから先をフラグメントとして扱われてしまいます。
よくある質問
なぜ韓国語や日本語、中国語の1文字をエンコードすると、長いパーセント記号の並びになるのですか?
これらの文字は基本的なASCIIの範囲外にあり、UTF-8では複数のバイト(多くのCJK文字では一般的に3バイト)で表現されるためです。そのバイトひとつひとつが別々の%XXとしてエンコードされるため、短い非ラテン文字の文字列でも、エンコード後にはかなり長い文字列になります。
スペースを表す%20と+の実際の違いは何ですか?
%20はスペースを表す汎用的なパーセントエンコーディングで、URLのどこでも有効です。+でスペースを表すのは、application/x-www-form-urlencoded形式のフォームデータの中だけに当てはまる、より限定的で古い慣習であるため、その文脈以外で+を使うと、スペースではなく文字どおりのプラス記号として解釈されてしまうことがあります。
%25 は何を表していますか?
%記号そのものをエンコードした結果です。URLに %25 が見えるときは、%XXの%がもう一度エンコードされている二重エンコードの可能性が高く、元の値を一度デコードしてから確認すると原因を見つけやすくなります。
ブラウザのアドレスバーには日本語が表示されるのに、コピーすると%だらけになるのはなぜですか?
ブラウザが見やすさのためにデコードして表示しているだけで、実際に通信で使われるのはエンコードされたURLだからです。コピーしたときや、メール・チャットに貼り付けたときに%XXの形で表示されるのは、そのためです。
URL全体を encodeURIComponent で変換してもよいですか?
いいえ。:// や / も変換されてURLとして使えなくなります。URL全体には encodeURI、クエリの値やパスの1区間には encodeURIComponent と使い分けてください。ただし encodeURI は & や = を変換しないため、値の中にそれらが含まれる場合は適しません。
「~」(チルダ)はエンコードが必要ですか?
現在の標準では、チルダは非予約文字なのでエンコードは不要です。古い仕様や一部のライブラリでは %7E に変換されることがありますが、どちらも同じ意味で扱われます。