ブラウザのアドレスバーに日本語を入力すると、%E6%9D%B1%E4%BA%ACのような謎の文字列に変わることがあります。また、フォームで送信したデータがURLに含まれるとき、 スペースや記号が文字化けしたように見えた経験はないでしょうか。これらはすべてURLエンコード(パーセントエンコーディング)と呼ばれる仕組みによるものです。 この記事では、URLエンコードが必要な理由、仕組み、JavaScriptでの正しい使い方、 そしてよくあるトラブルを解説します。
URLエンコードとは
URLに使える文字はRFC 3986によって厳しく制限されています。 許可されているのは英数字(A〜Z、a〜z、0〜9)と、一部の記号(- _ . ~)のみです。 日本語・スペース・その他の特殊記号はそのままURLに含めることができません。
そこで登場するのがURLエンコードです。許可されていない文字を%と16進数2桁の組み合わせに変換することで、あらゆる文字をURLの中で安全に扱えるようにします。 「パーセントエンコーディング」という名前はこの「%記法」に由来します。
URLで使える文字・使えない文字
| 分類 | 文字の例 | エンコードの要否 |
|---|---|---|
| 非予約文字 | A-Z a-z 0-9 - _ . ~ | 変換不要 |
| 予約文字 | : / ? # [ ] @ ! $ & ' ( ) * + , ; = | 文脈次第で変換が必要 |
| それ以外 | 日本語・スペース・特殊記号など | 必ず変換が必要 |
予約文字はURLの構造(パスの区切り/、 クエリの始まり?、 フラグメント#など)を表すために使われています。 これらをクエリパラメータの値の中に含めたいときは、URLエンコードで別の文字として扱わせる必要があります。 また、スペースの扱いには注意が必要で、クエリ文字列では+と%20の2通りが存在します(詳細は後述)。
エンコードの仕組み
URLエンコードは次の2ステップで行われます。
- 文字をUTF-8のバイト列に変換する
- 各バイトを
%XX形式(Xは16進数1桁)に変換する
たとえば「東京」をURLエンコードする場合は以下の流れになります。
「東」→ UTF-8: E6 9D B1 → %E6%9D%B1
「京」→ UTF-8: E4 BA AC → %E4%BA%AC
なぜUTF-8なのかというと、RFC 3986とHTML5の仕様でUTF-8が推奨されているからです。 古いシステムではShift_JISを使うURLも存在しましたが、現代のWebではUTF-8が標準です。 UTF-8の「東」は3バイト、「京」も3バイトなので、「東京」の2文字で合計6バイト(12文字のパーセント表記)になります。
JavaScriptでの使い分け
JavaScriptにはURLエンコード関連の関数が2組あります。エンコードする対象が「URL全体」なのか 「URLの一部(クエリパラメータの値など)」なのかによって使い分けます。
| 関数 | エンコードしない文字 | 用途 |
|---|---|---|
| encodeURI() | A-Z a-z 0-9 - _ . ~ : / ? # [ ] @ ! $ & ' ( ) * + , ; = | URL全体をエンコードするとき |
| encodeURIComponent() | A-Z a-z 0-9 - _ . ~ | クエリパラメータの値などURLの一部をエンコードするとき |
| decodeURI() | — | encodeURI() でエンコードしたURLをデコードするとき |
| decodeURIComponent() | — | encodeURIComponent() でエンコードした値をデコードするとき |
実際の使用例です。検索キーワードをURLに含める場合はencodeURIComponentを使います。
// NG: & が URL の区切り文字として解釈されてしまう
const url = '/search?q=' + '東京&2026'
// OK: クエリの値部分だけを encodeURIComponent でエンコード
const url = '/search?q=' + encodeURIComponent('東京&2026')
// → /search?q=%E6%9D%B1%E4%BA%AC%262026
よくあるトラブル
スペースが「+」になるケース
HTMLフォームを送信するときのデータ形式(application/x-www-form-urlencoded)では、 スペースは+としてエンコードされます。 これはRFC 3986のパーセントエンコーディング(スペース→%20)とは別の仕様です。 受け取るサーバーや処理ライブラリがどちらの形式を想定しているかによって解釈が変わるため、 混在すると意図しない文字列が生まれることがあります。
二重エンコードの罠
すでにエンコード済みの文字列を再度エンコードしてしまう「二重エンコード」は典型的なバグです。 たとえば%20をもう一度エンコードすると%2520(%が%25になる)になり、 サーバーでデコードしても%20という文字列が残ります。 予防策は「エンコードするのは必ず生のデータに対して行う」「デコード済みの値を受け取った後に再度エンコードする」という一方向の流れを徹底することです。
「%2F」(スラッシュ)がパスに含まれると403になる
スラッシュ/をエンコードすると%2Fになりますが、 Apacheなどの一部のWebサーバーはパスの中に%2Fが含まれると セキュリティ上の理由から403エラーを返すことがあります(AllowEncodedSlashes設定による)。 URLのパス部分にスラッシュを含む値を埋め込む設計は避け、代わりにクエリパラメータで渡すかパスを再設計することを検討してください。
ブラウザツールで手軽に確認する
URLエンコード・デコードを手軽に試したい場合は、URLエンコード/デコードツールが便利です。テキストを入力するだけでリアルタイムにエンコード・デコードでき、 日本語や特殊記号がどのように変換されるかをすぐに確認できます。 また、エンコードとBase64の違いを確認したい場合はBase64エンコード/デコードツールと並べて試してみると理解が深まります。 どちらのツールもブラウザ内で処理が完結するため、機密情報を含む文字列でも安心して使えます。
まとめ
- URLに使える文字はRFC 3986で英数字と一部記号のみに制限されており、日本語などはそのまま含められない
- URLエンコード(パーセントエンコーディング)は、許可されていない文字をUTF-8バイト列に変換してから
%XX形式で表す仕組み - JavaScriptでは「URL全体」には
encodeURI、「URLの一部(クエリパラメータの値など)」にはencodeURIComponentを使う - スペースの表現は
%20(RFC 3986)と+(フォームデータ形式)の2種類があり、使用する文脈によって異なる - 二重エンコードは「すでにエンコード済みの値を再エンコード」することで起きる典型的なバグ。常に生データに対してエンコードを適用する
- URLエンコードはURLでの文字伝送に、Base64エンコードはバイナリデータのテキスト化に使う。目的が異なる