fix(tanstack): keep empty search values so value-less query params stop redirecting (307)
Problem
decoStringifySearch (packages/tanstack/src/sdk/router.ts) skips "" values. On the server, TanStack Router's beforeLoad builds the normalized location with stringifySearch(parseSearch(search)), compares it with the request's publicHref and throws a 307 redirect to the normalized URL when they differ (@tanstack/router-core router.js, beforeLoad; on 1.166.x publicHref is the raw href in both branches, on 1.171.x still in the rewrite branch). A value-less parameter parses to "", so dropping it changes the href and every such request redirects.
Reproduction (measured on the site)
GET /s?q=camisa&sort= → 307 Location: /s?q=camisa; GET /?utm_source= → 307 Location: /. Production (Fresh) answers 200 without redirecting. Campaign links and the search form with an empty sort both hit this.
Root cause
The serializer was not a fixed point of the parser. decoParseSearch("?sort=") → { sort: "" }; decoStringifySearch({ sort: "" }) → "". TanStack's own default serializer (qss.encode) only drops undefined, so this divergence was specific to the deco serializer.
Fix
Keep "" (emitted as key=). undefined/null still remove a parameter, which is how navigate({ search: { q: undefined } }) clears one — the serializer's purpose (URLSearchParams format, repeated keys for arrays, VTEX filter.* round-trip) is unchanged.
Tests
New packages/tanstack/src/sdk/router.test.ts: round-trip fixed point for ?q=camisa&sort=, ?utm_source=, ?a=&b=, repeated keys and encoded values; undefined/null still dropped; "" kept. (%20 → + re-encoding by URLSearchParams is a separate, pre-existing normalization, noted in the test and out of scope.)
vitest run packages/tanstack/src/sdk/router.test.ts: 9/9. tsc --noEmit clean for packages/tanstack; biome check clean.
Found while validating deco-sites/oficina-tanstack#479.
🤖 Generated with Claude Code
Summary by cubic
Fixes 307 redirects for URLs with value-less query params like ?sort= and ?utm_source=. decoStringifySearch dropped "" values, so TanStack Router's server-side beforeLoad saw the parsed and serialized search differ from the request href and redirected to the normalized URL.
""is now kept and serialized askey=;undefined/nullstill remove a param, sonavigate({ search: { q: undefined } })keeps clearing values.- Adds round-trip tests in
packages/tanstack/src/sdk/router.test.tscovering empty values, repeated keys, and encoded strings.