URLパーセントエンコード(RFC 3986)とencodeURI vs encodeURIComponentの使い分け
RFC 3986 URI仕様、クエリ文字列での「+」と「%20」の空白解釈トラブル、UTF-8マルチバイト変換、JavaScriptの標準エンコード関数の正確な使い分けを解説します。
URL エンコーダー / デコーダー
データをサーバーに送信せず、すべてブラウザ内で安全に検証・実行できます。
RFC 3986標準でURLパーセントエンコードが不可欠な理由
インターネット標準RFC 3986で定義されたURI(Uniform Resource Identifier)は、厳格に制限されたUS-ASCII英数記号のみで構成されるよう規定されています。世界中のゲートウェイやメールルーター、中継プロキシが制御文字や非ASCII文字を誤認識・破損させないための安全設計です。
そのため、空白文字や日本語などのマルチバイト文字、およびURL構文上の区切り文字として予約されている特殊文字(:, /, ?, #, [, ], @)を安全に伝送するには、パーセントエンコーディング(URLエンコード)を行う必要があります。対象の文字をUTF-8バイト列に変換し、各バイトを%記号に続く2桁の16進数で表現します。
開発者が最も直面する問題:「+」記号と「%20」の空白の解釈差
Web開発において最も多発するエンコード事故の1つが、空白を表す際の+と%20の不一致です:
- RFC 3986標準URIパス(%20):
/api/files/sample%20test.pdfのようなURLパス部分では、空白は厳密に%20でなければなりません。パス内の+は予約された文字であり、文字通りのプラス記号として解釈されます。 - HTMLフォーム標準(+): 古典的なHTMLフォーム送信(
application/x-www-form-urlencoded)では、クエリ文字列内の空白を慣習的に+へ変換していました(?query=hello+world)。
バックエンドサーバーのパーサーによって、クエリ内の+を空白とみなすか、それとも加算記号として保持するかが異なり、検索クエリの破損やAPI署名の不一致を引き起こします。APIごとに1つの方式を決め、送信側と受信側で揃えるのが安全です。URLSearchParamsはフォームエンコードに従い空白を+に変換し(下の例を参照)、encodeURIComponentは常に%20を出力します。片方でエンコードしてもう片方でデコードすると、この種の不具合が起きます。
JavaScriptのエンコード関数比較:encodeURI vs encodeURIComponent
JavaScript標準ランタイムは、用途の異なる2つのグローバル関数を提供しています:
encodeURI(uri): 完全な1本のURL全体をエンコードする設計です。プロトコルやホスト、パスの区切りに必要な予約文字(; / ? : @ & = + $ , #)はあえて変換しません。もしクエリパラメータの値に&や?が含まれている場合、それらも変換されずに残るため、URL全体の構文が破壊されてしまいます。encodeURIComponent(str): 個々のクエリパラメータのキーや値をエンコードするための関数です。英数字と一部の安全な記号(A-Z a-z 0-9 - _ . ! ~ * ' ( ))を除くすべての予約文字をエスケープします。クエリ文字列の値を組み立てる際は、必ずencodeURIComponentを使用してください。
const param = "data&filter=active";
// ❌ 誤用:encodeURIは'&'や'='を変換しない
const badUrl = "https://api.devutils.run/search?query=" + encodeURI(param);
// 結果: https://api.devutils.run/search?query=data&filter=active ('query'と'filter'の2つのパラメータに分裂!)
// ✅ 正解:encodeURIComponentは'&'を'%26'へ安全にエスケープ
const safeUrl = "https://api.devutils.run/search?query=" + encodeURIComponent(param);
// 結果: https://api.devutils.run/search?query=data%26filter%3Dactive (1つのパラメータとして正常伝送)Unicode UTF-8マルチバイト文字のエンコード構造
アルファベット英字は1バイトで表現されますが、多言語文字はUTF-8仕様に従って2〜4バイトに拡張され、各バイトごとにパーセントエンコードされます:
- 英語 'A': ASCII 65(
0x41) -> 変換不要そのまま 'A' - 日本語ひらがな 'あ': Unicode
U+3042-> UTF-8 3バイト(0xE3 0x81 0x82) ->%E3%81%82 - 韓国語ハングル '가': Unicode
U+AC00-> UTF-8 3バイト(0xEA 0xB0 0x80) ->%EA%B0%80 - 絵文字 '🚀': Unicode
U+1F680-> UTF-8 4バイト(0xF0 0x9F 0x9A 0x80) ->%F0%9F%9A%80
古いShift_JISやEUC-JP前提のレガシーサーバーと通信する際は文字化けが発生するため、現代のWeb通信ではUTF-8を前提としたエンコーディングで統一する必要があります。
実務推奨:標準WHATWG URL APIによる安全な構築
文字列結合によるURL構築はエスケープ漏れの原因となります。ブラウザやNode.jsに組み込まれているWHATWG URLおよびURLSearchParams APIを使用すれば、パラメータのパーセントエンコードが自動化され、堅牢な通信が実現できます。
// WHATWG URL APIを使用した安全なクエリ構築
const endpoint = new URL("https://api.devutils.run/v1/search");
endpoint.searchParams.set("query", "機械学習 & AI");
endpoint.searchParams.set("tags", "react/nextjs");
console.log(endpoint.toString());
// 出力: https://api.devutils.run/v1/search?query=%E6%A9%9F%E6%A2%B0%E5%AD%A6%E7%BF%92+%26+AI&tags=react%2FnextjsURL エンコーダー / デコーダー
無料のオンラインURLエンコード・デコードツール。encodeURIComponentおよびencodeURIに対応。