URL encoding errors usually begin with the right function applied to the wrong unit. A complete URL, a path segment, a query value, and an application/x-www-form-urlencoded form body have different parsing rules. Identify the component before transforming text.
Let a URL parser own the structure
The WHATWG URL Standard defines parsing and percent-encoding behavior used by modern browsers. When constructing a URL in JavaScript, prefer the URL and URLSearchParams APIs over manual concatenation:
const url = new URL("https://example.test/search")
url.searchParams.set("q", "red & blue")
url.searchParams.append("tag", "api")
url.searchParams.append("tag", "security")
The API preserves the boundary between the query parameter name and value. Manual text such as ?q=red & blue would turn the ampersand into a separator.
Encode a component, not an already assembled URL
encodeURIComponent is appropriate for an individual component because it escapes characters such as &, =, ?, and # that otherwise have structural meaning. Applying it to an entire URL also escapes the colon and slashes, producing text that is no longer a normal absolute URL.
Avoid double encoding. The value a%2Fb already contains an encoded slash. Encoding the percent sign again produces a%252Fb; one decode returns the original encoded text rather than a/b.
Treat plus signs deliberately
In URL query serialization used for form-style data, a space may appear as +. A literal plus sign must therefore be encoded as %2B when the receiver uses form decoding. Outside that context, the URL standard’s percent-decoding algorithm does not universally mean “replace every plus with a space.” Use the same query parser on both ends.
Preserve repeated parameters
Queries can contain the same name more than once: tag=api&tag=security. Converting immediately to a plain object can discard one value. Use getAll or a multimap representation when repetition is meaningful.
Validate redirects after parsing
For callback and redirect parameters, encoding is not a security control. Parse the decoded destination, require an allowed scheme, compare the normalized host against an allowlist, and reject credentials or unexpected ports. String-prefix checks are vulnerable to lookalike hosts such as trusted.example.attacker.test.
Reproducible edge cases
Test the exact producer and consumer with:
- A space and a literal plus sign.
- Ampersand, equals, hash, slash, and percent characters.
- Repeated keys and blank values.
- Non-ASCII text and emoji.
- An already percent-encoded value.
- A malformed percent sequence such as
%ZZ.
Compare parsed key/value pairs, not only the serialized string, because equivalent URLs can have different textual representations.