MDN URLSearchParams - discussion (2023-01-25)
URLSearchParams is simple, but apps get complicated when the URL becomes a state model by accident. I'm curious how people use URLSearchParams responsibly in route-first apps: URL as map + hint, route state as truth, and evidence rendered so you can see what won.
Which keys do you keep in the URL (shareable) vs only in route state (motion)? How do you avoid parsing/serializing in multiple components and drifting behavior? Do you normalize params (ordering, defaults) before using them as cache keys or logs?
Comments (12)
Back to latestWe treat URL as hint and route state as truth.
But we always re-serialize the shareable subset back into the URL so deep links still work.
One module owns parse + serialize. Components never call new URLSearchParams().
ts
export type View = { q: string; sort: 'new' | 'top'; page: number };
export function parseView(search: string): View {
const p = new URLSearchParams(search);
const q = p.get('q') ?? '';
const sort = (p.get('sort') as View['sort']) ?? 'new';
const page = Number(p.get('page') ?? '1');
return { q, sort, page: Number.isFinite(page) ? page : 1 };
}Counterpoint: URL-as-hint can make deep links feel unreliable if you don't re-serialize route truth.
If the URL doesn't match the UI, users lose trust quickly.
Yep. Hint only works if you have a consistent "URL reflects truth" step.
Otherwise you get the worst of both worlds: URL isn't truth and also doesn't reflect truth.
We keep only user-meaningful filters in the URL (q, sort) and keep volatile posture in route state (panel, overlay, density).
It keeps links shareable without turning the URL into a state dump.
Normalization matters if you use query strings as cache keys.
We sort keys before serializing so ?a=1&b=2 and ?b=2&a=1 don't become different cache entries.
We render evidence for URL-derived keys: data-q, data-sort, data-page in debug mode.
It makes URL parsing bugs obvious.
A misunderstanding I see: teams treat URLSearchParams as a router.
String manipulation isn't navigation intent. Route contracts are.
We had drift bugs where two components used different defaults (sort=recent vs sort=new).
Central parse/serialize fixed it immediately.
Docs tie-in: this is exactly the routing posture in this repo: URL gives where; state gives what.
Related: Routing and Navigation and The App Router Mindset.
Counterpoint: not putting enough in the URL can make sharing frustrating.
We compromised: include the keys users care about, keep the rest internal.
We store a derived shareUrl string in state as evidence so users can copy a canonical link for the current posture.
It prevented subtle mismatches between what the UI shows and what the URL encodes.
URLSearchParams is an API primitive; the app model is the hard part. Make the model explicit and the API becomes boring.