Capítulo 28 de 57

Chapter 28: Custom Search Param Serialization

Core Idea

TanStack Router defaults to JSON.stringify/JSON.parse (with URL-escaping) for search param serialization, but the parseSearch/stringifySearch router options, paired with the parseSearchWith/stringifySearchWith helpers, let you swap in any custom format (base64, query-string, JSURL2, Zipson) as long as it's idempotent.

Key Concepts

  • Default Behavior: parseSearch: parseSearchWith(JSON.parse) and stringifySearch: stringifySearchWith(JSON.stringify); nested objects get JSON-encoded then URL-escaped, e.g. filters: {...} becomes filters=%7B%22author%22...%7D.
  • parseSearchWith(parseFn) / stringifySearchWith(stringifyFn): Router helper functions that adapt a raw parse/stringify function pair into the shape the router's parseSearch/stringifySearch options expect.
  • Idempotency Requirement: Serializing then deserializing must reproduce the original object exactly; a lossy format (e.g. one that can't represent nested objects) will silently drop data.
  • Base64 Encoding: Wrapping JSON.stringify/JSON.parse with safe binary encode/decode helpers (not raw atob/btoa, which mishandle non-UTF8 characters) produces base64-encoded search values for compatibility with URL unfurlers; carries a collision risk if user input is base64-encoded without care.
  • query-string library integration: A drop-in replacement offering a different, widely-recognized serialization format (filters=author%3Dtanner%26min_words%3D800).
  • JSURL2 / Zipson: Compression-oriented libraries usable the same way, producing shorter/differently-formatted query strings while preserving structure.
  • Safe Binary Encode/Decode Helpers: Documented utility functions (encodeToBinary/decodeFromBinary) that correctly round-trip non-UTF8 characters through btoa/atob, required whenever combining JSON with base64.

Code Examples

import { createRouter, parseSearchWith, stringifySearchWith } from '@tanstack/react-router'

const router = createRouter({
  parseSearch: parseSearchWith((value) => JSON.parse(decodeFromBinary(value))),
  stringifySearch: stringifySearchWith((value) => encodeToBinary(JSON.stringify(value))),
})

function decodeFromBinary(str: string): string {
  return decodeURIComponent(
    Array.prototype.map.call(atob(str), (c) => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)).join(''),
  )
}

function encodeToBinary(str: string): string {
  return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, (match, p1) => String.fromCharCode(parseInt(p1, 16))))
}
  • What it demonstrates: Base64-encoding JSON search params using safe binary helpers to avoid corrupting non-ASCII characters.

Key Takeaways

  1. Any custom serializer must be round-trip safe (idempotent); test that parse(stringify(x)) deep-equals x for representative data, including nested structures.
  2. Never use raw atob/btoa directly on arbitrary strings for search param encoding, use the documented encodeToBinary/decodeFromBinary helpers to avoid corruption on non-UTF8 input.
  3. Choose a serialization library based on tradeoffs: query-string for broad compatibility/readability, JSURL2/Zipson for compactness, base64 for URL-unfurler compatibility, plain JSON (default) for simplicity and debuggability.

Connects To

  • Ch 27: Search Params, the JSON-first default this chapter customizes.