fix(tanstack): keep empty search values so value-less query params stop redirecting (307)

Merged
AL
9d ago
+47-1

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 as key=; undefined/null still remove a param, so navigate({ search: { q: undefined } }) keeps clearing values.
  • Adds round-trip tests in packages/tanstack/src/sdk/router.test.ts covering empty values, repeated keys, and encoded strings.