URL エンコードの仕様と「日本語が文字化けする」を本気で直すガイド
執筆: ありがっさまりょうた
デジタル道具屋を個人で開発・運用しています。記事は実装時の検証結果と公式仕様を照合して執筆しています。
本記事の執筆方針と編集ポリシーについてはAbout ページをご覧ください。
導入:URL に日本語を入れると「%」だらけになる理由
日本語を含む URL をチャットなどに貼り付けると、https://example.com/?q=%E3%81%82%E3%81%84 のように 「%」と 16進数が並んだ形になることがあります。これは URL が壊れたわけではなく、percent-encoding(パーセントエンコード)と呼ばれる、ASCII 以外の文字を URL で安全に運ぶための正規の表現です。%E3%81%82 は「あ」を UTF-8 で表した 3バイト(0xE3 0x81 0x82)をそのまま 16進数で書いたものです。
URL の仕様(RFC 3986)では、ASCII の限られた文字以外は percent-encoding しなければ URL に含められないと定められています。 そのため日本語を URL に含める場合、この変換は必ず行われます。
1. percent-encoding の仕組み
URL では 「予約文字(reserved characters)」と「非予約文字(unreserved characters)」が区別されます。 非予約文字(英数字とごく一部の記号)以外を URL の意味のある場所に入れたい場合、各バイトを %XX という 16進数表記に変換します。
'あ' → UTF-8: 0xE3 0x81 0x82 → "%E3%81%82"
'A' → ASCII: 0x41 → "A"(変換不要)
' ' → ASCII: 0x20 → "%20" もしくは "+"
'?' → ASCII: 0x3F → "%3F"(クエリ区切りと混同しないため)重要なのは、percent-encoding が変換するのは「文字」ではなく「バイト列」だという点です。 同じ文字でも文字コードが違えばバイト列が変わり、結果も変わります。 現在の URL 標準(WHATWG URL Standard)や JavaScript の encodeURIComponent は常に UTF-8 を使いますが、 古いシステムが Shift_JIS でエンコードした URL を UTF-8 として解釈すると文字化けが起こります。
encodeURIComponent('日本')
// → "%E6%97%A5%E6%9C%AC" (UTF-8: 日 = E6 97 A5, 本 = E6 9C AC)
// 同じ「日本」を Shift_JIS のバイト列で percent-encoding した場合
// → "%93%FA%96%7B" (Shift_JIS: 日 = 93 FA, 本 = 96 7B)
decodeURIComponent('%93%FA%96%7B')
// → URIError: URI malformed(UTF-8 として不正なバイト列のため)「日本語のクエリが文字化けする」「デコードでエラーになる」という場合は、 送信側と受信側で前提としている文字コードが一致しているかを最初に確認します。
2. encodeURI と encodeURIComponent の違い
JavaScript には encodeURI と encodeURIComponent の 2 つの関数があります。 両者の違いは、パスやクエリの「区切り記号」をそのまま残すかどうかです。
| 関数 | 対象 | ? や / |
|---|---|---|
encodeURI | URL 全体 | そのまま残す |
encodeURIComponent | クエリの値、フラグメント等の「断片」 | エンコードする |
区切り記号を含む値を渡すと、この違いが結果に直接表れます。
encodeURI('a?b&c') // → "a?b&c" (? と & はそのまま)
encodeURIComponent('a?b&c') // → "a%3Fb%26c" (? → %3F, & → %26)
// クエリの値に encodeURI を使うと、& が区切りとして解釈され値が分断される
'https://example.com/?q=' + encodeURI('a&b') // → ?q=a&b (q は "a" になる)
'https://example.com/?q=' + encodeURIComponent('a&b') // → ?q=a%26b (q は "a&b" になる)したがって、クエリパラメータの値には encodeURIComponent を使うのが原則です。encodeURI は「区切り記号を含む URL 全体をまとめて変換したい」という用途に限られます。 さらに安全な方法は、URL オブジェクトと URLSearchParams に組み立てを任せることです:
// 推奨:URLSearchParams を使う
const params = new URLSearchParams({ q: '東京 ラーメン', page: '2' });
const url = `https://example.com/search?${params.toString()}`;
// → https://example.com/search?q=%E6%9D%B1%E4%BA%AC+%E3%83%A9%E3%83%BC%E3%83%A1%E3%83%B3&page=2URLSearchParams はキーと値の両方に必要な percent-encoding を自動で適用するため、 文字列を手作業で連結する方法よりもエンコード漏れが起こりにくくなります。
3. 「+ はスペース」問題
URL のクエリ部分では、スペースが %20 ではなく + で表されることがあります。 これは application/x-www-form-urlencoded(HTML フォームの送信形式)の歴史的経緯です。パス部分では %20、クエリ部分では + または %20、というのが現在の事実上のルール。
デコード側でも注意が必要:
decodeURIComponent('東京+ラーメン') // → "東京+ラーメン"(+ は変換されない)
new URLSearchParams('q=東京+ラーメン').get('q') // → "東京 ラーメン"(+ がスペースに)4. 日本語ファイル名のダウンロード
Content-Disposition: attachment; filename="日本語.pdf" のような書き方は、 ASCII 以外の扱いが歴史的に混乱しています。 現在は RFC 5987 に従い、以下のように書くのが標準です:
Content-Disposition: attachment;
filename="report.pdf";
filename*=UTF-8''%E6%97%A5%E6%9C%AC%E8%AA%9E.pdffilename(旧)に ASCII フォールバック、filename*(新)に RFC 5987 形式で日本語を入れます。 モダンブラウザは filename* を優先するため、日本語ファイル名で正しくダウンロードされます。
5. SNS シェア用 URL の組み立て
X(Twitter)や LINE のシェア URL を生成するとき、ユーザー入力をそのまま連結すると壊れます:
// 失敗:& や # が混入すると壊れる
const url = `https://twitter.com/intent/tweet?text=${title}&url=${pageUrl}`;
// 正しい
const url = new URL('https://twitter.com/intent/tweet');
url.searchParams.set('text', title);
url.searchParams.set('url', pageUrl);
window.open(url.toString());6. 試して確かめる
URL に含まれる謎の %XX をデコードしたり、自分の文字列を percent-encoding したりするには、URL エンコード・デコードツールが便利です。 クエリパラメータのデバッグや、SNS シェア URL の構築にもどうぞ。
関連: HTML エンティティ変換(HTML 上で < を表示する用)、Base64 変換(バイナリを URL に乗せる別手段)。
よくある失敗パターン
OAuth の state や redirect_uri のように、+・=・& を含みうる値を エンコードせずに連結すると、受信側で値が分断されたり書き換わったりして認証フローが失敗します。 たとえば Base64 形式の state に含まれる + は、クエリではスペースとして解釈されます。 URL を組み立てるときは文字列連結ではなく URL オブジェクトとURLSearchParams を使うことで、この種のエンコード漏れは構造的に防げます。
参考にした一次情報
本記事の内容は、以下の公式仕様や一次情報を参照して執筆しています。
- RFC 3986 - Uniform Resource Identifier (URI): Generic Syntax
IETF
https://www.rfc-editor.org/rfc/rfc3986
- URL Living Standard
WHATWG
https://url.spec.whatwg.org/
- encodeURIComponent() - JavaScript | MDN
MDN Web Docs
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent
この記事の内容を実際に試す
解説した内容は、以下のツールでブラウザ上から無料で試せます。