Toolypet
ブログに戻る
Dev

URLエンコーディング完全理解 - 文字化けと特殊文字問題の解決

API呼び出しで日本語が文字化けし、特殊文字がエラーを起こす理由。encodeURI vs encodeURIComponentの違いと実践的な解決法。

Toolypet Team

Toolypet Team

Development Team

3 分で読めます

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全体encodeURIURL構造を維持
パスセグメント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  +

どちらも空白ですが、由来が異なります。

方式使用場所
%20URL標準(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 ParserURL構造分析
Base64Base64変換
URLencodingdecodingWeb開発APIクエリストリング

著者について

Toolypet Team

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.

Web DevelopmentCSS ToolsDeveloper ToolsSEOSecurity