Why encode at all
URLs only allow a limited character set. Chinese characters, spaces, &, =, ? and friends must be written as %XX sequences to travel safely, otherwise parsers mistake them for structural separators.
%20 vs +
Inside query strings (application/x-www-form-urlencoded) a space was historically written as +; in the URL path it can only be %20. Decoders disagree on how to treat +, which is where "my spaces turned into plus signs" bugs come from, prefer %20 for spaces in parameter values.
encodeURI vs encodeURIComponent
- encodeURI: keeps URL structure characters (
: / ? # [ ] @), use it for a whole URL; - encodeURIComponent: encodes aggressively, use it for individual parameter values.
Applying encodeURIComponent to a full URL encodes the :/ too and breaks the link, the most common misuse.
Two roots of Chinese mojibake
- Asymmetric encoding: encoded twice but decoded once (or the reverse), leaving %XX residue;
- Charset mismatch: sent as UTF-8 but parsed as GBK on the server, guaranteed mojibake.
Common characters and their encoding
| Character | Encoded | Notes |
|---|---|---|
| Space | %20 or + | Query strings historically use +; paths must use %20 |
| Non-ASCII letters | %E4%B8%AD | Each UTF-8 byte becomes one %XX |
| & | %26 | Otherwise parsed as a parameter separator |
| = | %3D | Must be escaped inside parameter values |
| # | %23 | Otherwise truncates the URL as a fragment |
Code examples
// Frontend: always encodeURIComponent when building parameters
const url = '/search?q=' + encodeURIComponent('a b & c');
// => /search?q=a%20b%20%26%20c
// Node: new URLSearchParams({ q: 'a b & c' }).toString()
// Python: urllib.parse.quote('a b & c', safe='')
Three debugging tips
- When unsure, decode once first to check whether the value was encoded twice;
- On the server, confirm whether the framework decodes
+as a space, in a URL path it is a literal plus; - For non-ASCII mojibake, verify that both ends use UTF-8 before re-encoding anything.
Try them: URL codec, Base64 codec