MDN URLSearchParams - discussion (2023-02-07)
URLSearchParams is deceptively simple: it invites you to treat strings as a router. The MDN page is a helpful anchor because it reminds you what the primitive actually does, which makes it easier to design a stable parse/serialize contract instead of ad-hoc string manipulation.
Do you centralize parse/serialize for query params, or allow components to read params directly? Which keys do you consider share-worthy (belong in URL) versus route-doc-only state? How do you prevent default drift (two components disagree on default sort/filter)?
Comments (16)
Back to latestCentral parse/serialize is non-negotiable for us.
If two components parse the same key differently, you get a "sometimes" bug that never dies.
We treat query parsing as a contract and we log normalization decisions so they're not invisible:
txt
[tips] query parse q="react" sort=null -> sort="recent" reason=missingDefault
[tips] query normalize removed="page" reason=invalidNumber raw="-1"
[tips] history replaceState to=/search?q=react&sort=recent reason=normalize:query
If the URL changes without that log line, it feels haunted.
Counterpoint: not everything belongs in the URL, even if it's easy to serialize.
We keep URL keys to things users care about when sharing (category, sort, query). Panel state and drafts stay internal.
Same. I like the framing: URL is for human-meaningful keys; docs are for machine-meaningful keys.
If a key doesn't improve sharing, it usually shouldn't be in the URL.
Default drift is real. We solved it by deriving a canonical query object and using it everywhere.
If a component needs a default, it asks the canonical parser, it doesn't invent one.
Long-form argument: query params become a schema whether you admit it or not.
If you don't name the schema and version it, you'll end up with accidental backwards compatibility issues and break sharing links silently.
Central parse/serialize + normalization logs are a cheap form of governance.
We also render a data-query-contract summary in debug mode so screenshots are self-contained.
It prints like q=react sort=recent page=1 and settles arguments fast.
Counterpoint: normalization is dangerous if you do it too aggressively (you can erase user intent).
We log what we changed and we only normalize when we're confident the user didn't mean it (invalid numbers, unknown enums).
A nice test strategy: assert on the normalized URL (or the query contract summary) rather than component internals.
If the contract says sort=recent, the UI should reflect it.
Long-form counterpoint: URL params are an API surface, and people forget that.
Once your links are shared in docs, tickets, and chat, changing param meaning is a breaking change. Treat it like a versioned API.
Docs tie-in: this maps cleanly to the routing docs: URL is a calm surface; state docs are the richer model.
Related: Routing and Navigation and The App Router Mindset.
We also keep a shareUrl derived string as evidence (the canonical URL for the current posture).
It prevents subtle mismatches where the UI is showing one thing and the share link encodes another.
MDN isn't glamorous, but it's a stable foundation.
Once you accept how URLSearchParams behaves, you stop fighting it and start designing real contracts around it.
We used enums for query keys and values (typed), and it reduced drift bugs.
The parser becomes the only place where strings exist; the rest of the app uses the typed contract.
A small improvement: log query parse failures with the raw value, but don't crash.
If you crash on unknown params, you've made sharing links fragile.
Long-form: normalization logs are also a governance tool.
When you see frequent normalizations for one key, it usually means the UI is letting invalid values be produced. Fix the UI, not the parser.
If you want one rule: never let components parse params directly.
Centralize it and make it produce a query contract summary that you can render and log.