URLエンコーディング完全理解 - 文字化けと特殊文字問題の解決
API呼び出しで日本語が文字化けし、特殊文字がエラーを起こす理由。encodeURI vs encodeURIComponentの違いと実践的な解決法。
Toolypet Team
Development Team
URLエンコーディング完全理解
「APIに日本語パラメータを送ったら文字化けして返ってきました。」
送信: ?name=田中太郎
受信: ?name=ç°ä¸å¤ªé
この問題の原因はURLエンコーディングです。正しく理解すれば、二度と文字化けで悩むことはありません。
なぜエンコーディングが必要か
URLは1990年代に作られ、ASCII文字のみを許可しています。
URLで安全な文字
A-Z a-z 0-9 - _ . ~
これが全てです。
URLで問題になる文字
| 文字 | 問題 |
|---|---|
| 日本語 | ASCIIではない |
| 空白 | URL区切り文字として解釈される |
& | パラメータ区切り |
? | クエリストリング開始 |
= | キー・値の区切り |
# | フラグメント開始 |
/ | パス区切り |
エンコーディングが行うこと
問題のある文字を%XX形式(16進数)に変換します。
空白 -> %20
& -> %26
田 -> %E7%94%B0 (UTF-8基準で3バイト)
JavaScriptエンコーディング関数
重要な違い
const text = "hello world&name=田中太郎";
encodeURI(text);
// "hello%20world&name=%E7%94%B0%E4%B8%AD%E5%A4%AA%E9%83%8E"
// & と = はそのまま(URL構造を維持)
encodeURIComponent(text);
// "hello%20world%26name%3D%E7%94%B0%E4%B8%AD%E5%A4%AA%E9%83%8E"
// & -> %26, = -> %3D(全てエンコード)
いつ何を使うか
| 状況 | 関数 | 理由 |
|---|---|---|
| クエリパラメータの値 | encodeURIComponent | &、=もエンコードが必要 |
| URL全体 | encodeURI | URL構造を維持 |
| パスセグメント | encodeURIComponent | /もエンコードが必要 |
間違いやすいケース
// NG: URL全体をencodeURIComponent
const url = "https://api.com/search?q=テスト";
encodeURIComponent(url);
// "https%3A%2F%2Fapi.com%2Fsearch%3Fq%3D%E3%83%86%E3%82%B9%E3%83%88"
// URLとして使用不可!
// OK: 値のみエンコード
const query = "テスト";
const url = `https://api.com/search?q=${encodeURIComponent(query)}`;
// "https://api.com/search?q=%E3%83%86%E3%82%B9%E3%83%88"
URLSearchParams - モダンな方法
直接エンコーディングする代わりにURLSearchParamsを使いましょう。
基本的な使い方
const params = new URLSearchParams({
name: "田中太郎",
city: "東京都渋谷区",
tags: "開発者,フロントエンド"
});
params.toString();
// "name=%E7%94%B0%E4%B8%AD%E5%A4%AA%E9%83%8E&city=%E6%9D%B1%E4%BA%AC%E9%83%BD%E6%B8%8B%E8%B0%B7%E5%8C%BA&tags=%E9%96%8B%E7%99%BA%E8%80%85%2C%E3%83%95%E3%83%AD%E3%83%B3%E3%83%88%E3%82%A8%E3%83%B3%E3%83%89"
自動的にエンコードされます。ミスする余地がありません。
URLオブジェクトと一緒に
const url = new URL("https://api.com/search");
url.searchParams.set("q", "田中太郎");
url.searchParams.set("page", 1);
url.toString();
// "https://api.com/search?q=%E7%94%B0%E4%B8%AD%E5%A4%AA%E9%83%8E&page=1"
パース(デコード)
const url = new URL("https://api.com/search?name=%E7%94%B0%E4%B8%AD%E5%A4%AA%E9%83%8E");
url.searchParams.get("name"); // "田中太郎" <- 自動デコード!
実践的な問題解決
問題1:二重エンコーディング
// すでにエンコードされた値をさらにエンコード
const encoded = "%E7%94%B0%E4%B8%AD%E5%A4%AA%E9%83%8E";
encodeURIComponent(encoded);
// "%25E7%2594%25B0%E4%B8%AD%E5%A4%AA%E9%83%8E"
// % -> %25 に変換されて完全に文字化け
解決策:エンコード前にすでにエンコードされているか確認
function safeEncode(str) {
try {
// すでにエンコードされていたら、デコードしてから再エンコード
return encodeURIComponent(decodeURIComponent(str));
} catch {
// デコード失敗 = エンコードされていない状態
return encodeURIComponent(str);
}
}
問題2:空白の二つの顔
%20 vs +
どちらも空白ですが、由来が異なります。
| 方式 | 使用場所 |
|---|---|
%20 | URL標準(RFC 3986) |
+ | HTMLフォーム(application/x-www-form-urlencoded) |
encodeURIComponent("hello world"); // "hello%20world"
new URLSearchParams({q: "hello world"}).toString(); // "q=hello+world"
どちらもサーバーで正しく処理されます。ただし、一貫性のために1つの方式を選んでください。
問題3:フレームワークがすでにエンコードしている
axios、fetch、React Routerなどは自動的にエンコードします。
// axios - 自動エンコード
axios.get('/search', { params: { name: '田中太郎' } });
// リクエスト: /search?name=%E7%94%B0%E4%B8%AD%E5%A4%AA%E9%83%8E
// NG: 直接エンコードすると二重エンコード
axios.get('/search', { params: { name: encodeURIComponent('田中太郎') } });
// リクエスト: /search?name=%25E7%2594%25B0... (文字化け)
ルール:フレームワークを使用しているなら、直接エンコードしないでください。
サーバーサイド処理
Node.js / Express
// 自動デコードされる
app.get('/search', (req, res) => {
const name = req.query.name; // "田中太郎"
});
// パスパラメータも同様
app.get('/users/:name', (req, res) => {
const name = req.params.name; // "田中太郎"
});
Python / Flask
from flask import request
@app.route('/search')
def search():
name = request.args.get('name') # "田中太郎"
PHP
$name = $_GET['name']; // "田中太郎"
// PHPも自動デコード
特殊なケース
Base64 in URL
標準Base64には+、/、=があり、URLで問題になります。
// 標準Base64
btoa("hello"); // "aGVsbG8="
// URL-safe Base64
function base64UrlEncode(str) {
return btoa(str)
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=+$/, ''); // パディング削除
}
base64UrlEncode("hello"); // "aGVsbG8"
JWTはこの形式を使用しています。
Fragment (#) の注意点
https://example.com/page?name=田中太郎#section
↑
ここからサーバーに送信されない
#以降(フラグメント)はブラウザでのみ処理され、サーバーには送信されません。
デバッグのコツ
ブラウザのアドレスバー
ブラウザは可読性のためにデコードして表示します。
表示: https://example.com/users/田中太郎
実際: https://example.com/users/%E7%94%B0%E4%B8%AD%E5%A4%AA%E9%83%8E
開発者ツールのNetworkタブで実際のリクエストURLを確認してください。
curlでテスト
# 直接エンコードされたURL
curl "https://api.com/search?name=%E7%94%B0%E4%B8%AD%E5%A4%AA%E9%83%8E"
# curlにエンコードさせる
curl -G "https://api.com/search" --data-urlencode "name=田中太郎"
まとめ
| 状況 | 解決策 |
|---|---|
| クエリパラメータを作成する | URLSearchParamsを使用 |
| 値のみエンコード | encodeURIComponent |
| URL全体をエンコード | encodeURI |
| フレームワーク使用中 | 直接エンコードしない |
| 文字化け | 二重エンコードを疑う |
核心原則:可能な限りURLSearchParamsやフレームワークに任せて、直接エンコーディングは避けてください。
関連ツール
| ツール | 用途 |
|---|---|
| URL Encoder | エンコード/デコード |
| URL Parser | URL構造分析 |
| Base64 | Base64変換 |
著者について
Toolypet Team
Development Team
The Toolypet Team creates free, privacy-focused web tools for developers and designers. All tools run entirely in your browser with no data sent to servers.