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.
Real-world cases: three common failures
- "Spaces in my search term became +": the frontend encoded with form rules and the backend decoded with path rules. Use
%20for spaces in parameter values to avoid it. - "Chinese parameters arrive garbled": usually a charset mismatch (sent UTF-8, parsed GBK). Unify on UTF-8 at both ends instead of encoding/decoding repeatedly.
- "%2520 shows up in the link":
%20was encoded twice into%2520(the % became %25). Remove the duplicate encoding step.
FAQ
Can a path use + for a space? No — only query strings may treat + as space; in a path it is a literal plus. Can encodeURIComponent encode a whole URL? Not advisable; it encodes :/?&= too and breaks the structure. Why does one Chinese character encode to three %XX groups? Because UTF-8 uses three bytes for it, each percent-encoded: %E4%B8%AD. Does the browser encode Chinese in the address bar automatically? It displays it unencoded but still sends %XX, so when building URLs by hand, encode it yourself.
Try them: URL encoder/decoder, Base64 encoder/decoder
Conventions between frontend and backend
- Agree on who encodes: the frontend encodes once while building the link and the backend only decodes and validates — never both, or double encoding corrupts values.
- Treat names and values differently: parameter names are developer-defined and need no encoding; values come from users and must be encoded. Handling them together turns readable names into noise.
- Keep both forms in logs: log the decoded, readable content alongside the raw encoded string; logging only the encoded form turns debugging into manual decoding.
- Test boundary characters: spaces, plus signs, percent signs, Chinese text and emoji all break easily, so cover them in API tests rather than spot-checking by hand.
- Document the convention: state in the API docs whether a parameter expects an encoded or original value — one sentence saves hours of integration work.
Splitting encoding responsibility clearly and writing it into docs and tests is the durable fix for link-related bugs.
Interaction with gateways and proxies
When a request passes a reverse proxy or gateway, the path and query string may be rewritten. If a rule processes already-encoded percent signs again, values end up double-encoded or undecodable. Compare the address the client sent with the one the application received — the difference usually shows immediately which layer handled it one time too many.