URL に日本語や記号を含めるときのエンコード - encodeURI と encodeURIComponent の使い分け

URL に日本語やスペース、記号を含めると文字化けやリンク切れが起きることがある。JavaScript には encodeURI と encodeURIComponent という似た名前の関数があるが、使い分けを間違えると URL が壊れる。この記事ではパーセントエンコーディングの仕組みから、2 つの関数の違いと実務での使い方を解説する。

パーセントエンコーディングとは

URL に使える文字は ASCII の一部に限られている。RFC 3986 では、エンコードなしで使える文字(非予約文字)を以下のように定めている。

英字(A-Z, a-z)
数字(0-9)
ハイフン、ピリオド、アンダースコア、チルダ(- . _ ~)

これ以外の文字を URL に含めるには、その文字の UTF-8 バイト列を % に続く 16 進数 2 桁で表す「パーセントエンコーディング」が必要になる。

// 「東京」を UTF-8 でエンコードすると
// 東 → E6 9D B1 → %E6%9D%B1
// 京 → E4 BA AC → %E4%BA%AC

console.log(encodeURIComponent('東京'));
// → %E6%9D%B1%E4%BA%AC

ブラウザのアドレスバーでは日本語がそのまま表示されることが多いが、実際に送信される HTTP リクエストではパーセントエンコーディングされた文字列が使われている。

予約文字という概念

URL には構造上の意味を持つ「予約文字」が存在する。

文字URL 内での役割
:スキームとホストの区切り
/パスの区切り
?クエリの開始
&クエリパラメータの区切り
=キーと値の区切り
#フラグメントの開始

これらの文字はエンコードすべき場面とすべきでない場面がある。URL の構造を作るために使うなら生のまま、データとして含めるならエンコードが必要だ。この判断の違いが encodeURI と encodeURIComponent の役割を分ける。

encodeURI の動作

encodeURI は URL 全体をエンコードするための関数で、URL の構造に関わる予約文字はエンコードしない。

const url = 'https://example.com/検索結果?q=東京タワー&page=1';

console.log(encodeURI(url));
// → https://example.com/%E6%A4%9C%E7%B4%A2%E7%B5%90%E6%9E%9C?q=%E6%9D%B1%E4%BA%AC%E3%82%BF%E3%83%AF%E3%83%BC&page=1

:// や ? や & はそのまま残り、日本語だけがエンコードされている。URL の形を保ったまま、ASCII 以外の文字だけを安全な表現に変換するのが encodeURI の役割だ。

encodeURI がエンコードしない文字は、非予約文字(英数字と - . _ ~)に加えて、予約文字(: / ? # [ ] @ ! $ & ’ ( ) * + , ; =)とパーセント記号 % である。

URL の構造を形成する文字群。encodeURI はこれらを「URL の一部」とみなして変換しない。

encodeURIComponent の動作

encodeURIComponent は URL の部品(パラメータの値など)をエンコードするための関数で、予約文字もエンコードする。

const value = '東京タワー&スカイツリー';

console.log(encodeURIComponent(value));
// → %E6%9D%B1%E4%BA%AC%E3%82%BF%E3%83%AF%E3%83%BC%26%E3%82%B9%E3%82%AB%E3%82%A4%E3%83%84%E3%83%AA%E3%83%BC

& が %26 にエンコードされている点に注目してほしい。もしこの値を encodeURI でエンコードしていたら、& がそのまま残り、クエリパラメータの区切りとして誤解釈されてしまう。

encodeURI

URL 全体に使う。予約文字を保持するため、URL の構造が壊れない。パラメータの値に予約文字が含まれると問題が起きる。

encodeURIComponent

URL の部品(パラメータの値やパスの一部)に使う。予約文字もエンコードするため、データとして安全に渡せる。URL 全体に使うと構造が壊れる。

誤用するとどうなるか

2 つの関数を逆に使った場合の問題を具体的に見てみよう。

HTML
CSS
JavaScript
<div id="demo">
    <div class="case">
        <h2>ケース 1: URL 全体に encodeURIComponent を使う(誤り)</h2>
        <p id="case1" class="result"></p>
    </div>
    <div class="case">
        <h2>ケース 2: パラメータ値に encodeURI を使う(誤り)</h2>
        <p id="case2" class="result"></p>
    </div>
    <div class="case">
        <h2>ケース 3: 正しい使い分け</h2>
        <p id="case3" class="result"></p>
    </div>
</div>
#demo {
    font-family: monospace;
    font-size: 13px;
}
h2 {
    font-size: 13px;
    margin-bottom: 4px;
}
.case {
    margin-bottom: 16px;
}
.result {
    background: #1e1e1e;
    color: #d4d4d4;
    padding: 8px 12px;
    border-radius: 4px;
    word-break: break-all;
    line-height: 1.5;
}
.bad {
    border-left: 3px solid #f44;
}
.good {
    border-left: 3px solid #4caf50;
}
var base = 'https://example.com/search';
var query = 'price=100&200';

var case1 = encodeURIComponent(base + '?q=' + query);
var el1 = document.getElementById('case1');
el1.textContent = case1;
el1.className = 'result bad';

var case2 = base + '?q=' + encodeURI(query);
var el2 = document.getElementById('case2');
el2.textContent = case2;
el2.className = 'result bad';

var case3 = base + '?q=' + encodeURIComponent(query);
var el3 = document.getElementById('case3');
el3.textContent = case3;
el3.className = 'result good';

ケース 1 では :// や ? までエンコードされて URL として機能しなくなっている。ケース 2 では & がそのまま残り、“200” が別のパラメータとして分離されてしまう。ケース 3 のように、URL の構造部分はそのまま組み立て、値の部分だけ encodeURIComponent でエンコードするのが正しい。

実務でのパターン

よくある場面ごとに、どちらの関数を使うべきかを整理する。

クエリパラメータの値

encodeURIComponent を使う。ユーザー入力をそのまま URL に埋め込むと、& や = が構造を壊す危険がある。

パスの一部

encodeURIComponent を使う。日本語のカテゴリ名やファイル名をパスに含めるときに必要。ただし / はエンコードされるため、パス区切りは自分で組み立てる。

URL 全体の一括変換

encodeURI を使う。既に構造が完成している URL の中の非 ASCII 文字だけを変換したいとき。ただしパラメータ値の安全性は保証されない。

リダイレクト先 URL をパラメータに含める

encodeURIComponent を使う。URL の中に別の URL を埋め込むときは、内側の URL の予約文字をすべてエンコードする必要がある。

リダイレクト URL の埋め込みは特に間違いやすいパターンだ。

// リダイレクト先 URL をパラメータに含める
const redirectUrl = 'https://example.com/callback?token=abc';

// 誤り: encodeURI だと ? や = が残り、外側の URL 構造を壊す
const bad = 'https://auth.example.com/login?redirect=' + encodeURI(redirectUrl);
// → https://auth.example.com/login?redirect=https://example.com/callback?token=abc
// → ?token=abc が外側のパラメータとして解釈される

// 正しい: encodeURIComponent で内側の URL をまるごとエンコード
const good = 'https://auth.example.com/login?redirect=' + encodeURIComponent(redirectUrl);
// → https://auth.example.com/login?redirect=https%3A%2F%2Fexample.com%2Fcallback%3Ftoken%3Dabc

URLSearchParams を使う方法

クエリパラメータの組み立てには URLSearchParams を使うと、エンコードの手間を省ける。

const params = new URLSearchParams();
params.set('q', '東京タワー&スカイツリー');
params.set('page', '1');
params.set('redirect', 'https://example.com/callback?token=abc');

const url = 'https://example.com/search?' + params.toString();
console.log(url);
// → https://example.com/search?q=%E6%9D%B1%E4%BA%AC...&page=1&redirect=https%3A%2F%2F...

URLSearchParams は値を自動的にエンコードしてくれるため、encodeURIComponent を手動で呼ぶ必要がない。キーと値のペアを扱うなら、こちらを使うほうがミスが少なくなる。

手動で encodeURIComponent

柔軟性が高いが、エンコード忘れのリスクがある。URL の各部品を自分で組み立てる必要がある。

URLSearchParams

クエリパラメータの組み立てに特化しており、エンコードを自動で行う。ただしパス部分には使えない。

デコードの対応関係

エンコードした文字列を元に戻すには、対応するデコード関数を使う。

エンコードデコード対象
encodeURIdecodeURIURL 全体
encodeURIComponentdecodeURIComponentURL の部品
const encoded = '%E6%9D%B1%E4%BA%AC%E3%82%BF%E3%83%AF%E3%83%BC';

console.log(decodeURIComponent(encoded));
// → 東京タワー

// 不正なパーセントエンコーディングをデコードするとエラー
try {
    decodeURIComponent('%E6%9D');
} catch (e) {
    console.log(e.message);
    // → URI malformed
}

decodeURIComponent に不完全なバイト列を渡すと URIError が発生する。外部から受け取った文字列をデコードするときは try-catch で囲んでおくのが安全だ。

まとめの判断基準

ユーザーが入力した検索キーワードを URL のクエリパラメータに埋め込むとき、最も適切な方法はどれですか?

  • encodeURI(keyword) を使う
  • encodeURIComponent(keyword) を使う
  • keyword をそのまま文字列結合する
  • escape(keyword) を使う
__RESULT__

ユーザー入力には & や = などの予約文字が含まれる可能性があるため、encodeURIComponent でエンコードする必要があります。encodeURI は予約文字をエンコードしないため不適切です。escape は非推奨関数であり、UTF-8 を正しく扱えません。

判断は単純で、URL 全体を扱うなら encodeURI、URL の部品を扱うなら encodeURIComponent を使えばよい。迷ったら encodeURIComponent を選ぶほうが安全だ。実際の開発では URLSearchParams を使ってクエリパラメータを組み立てるのが最もミスの少ないアプローチになる。