diff --git a/docs/router/guide/ssr.md b/docs/router/guide/ssr.md index f87849f8a1..dc4d0324c7 100644 --- a/docs/router/guide/ssr.md +++ b/docs/router/guide/ssr.md @@ -356,15 +356,16 @@ Streaming dehydration/hydration is an advanced pattern that goes beyond markup a ## Data Serialization -When using SSR, data passed between the server and the client must be serialized before it is sent across network-boundaries. TanStack Router handles this serialization using a very lightweight serializer that supports common data types beyond JSON.stringify/JSON.parse. +When using SSR, data passed between the server and the client must be serialized before it is sent across network boundaries. TanStack Router handles this serialization and supports common values beyond `JSON.stringify`/`JSON.parse`. -Out of the box, the following types are supported: +Common supported values include: -- `undefined` +- JSON-compatible primitives, arrays, and plain objects +- `undefined` and `BigInt` - `Date` -- `Error` -- `FormData` +- `Error` objects (the message is preserved) +- `Map` and `Set` -If you feel that there are other types that should be supported by default, please open an issue on the TanStack Router repository. +Nested and cyclic references are preserved. TanStack Start also supports `FormData` as input to `POST` server functions, but `FormData` is not a general SSR-hydration serialization type. -If you are using more complex data types like `Map`, `Set`, `BigInt`, etc, you may need to use a custom serializer to ensure that your type-definitions are accurate and your data is correctly serialized and deserialized. We are currently working on both a more robust serializer and a way to customize the serializer for your application. Open an issue if you are interested in helping out! +For application-specific types, TanStack Start applications can register custom serialization adapters to preserve the type across serialization boundaries. diff --git a/docs/start/framework/react/guide/server-functions.md b/docs/start/framework/react/guide/server-functions.md index 903e5d7b86..d4020058c3 100644 --- a/docs/start/framework/react/guide/server-functions.md +++ b/docs/start/framework/react/guide/server-functions.md @@ -255,6 +255,48 @@ export const looseOutputServerFn = createServerFn({ > [!WARNING] > `strict: false` only relaxes TypeScript's serialization checks. Values still need to be handled correctly by the runtime serialization layer when they are sent between the client and server. Prefer the default `strict: true` unless you know why the default serializability rules are too restrictive for a specific server function. +## Custom Serialization Adapters + +TanStack Start uses registered serialization adapters both when hydrating server-rendered data on the client and when serializing server-function inputs, outputs, and errors. + +Create an adapter in a module that is available in both client and server environments: + +```tsx +// src/serialization.ts +import { createSerializationAdapter } from '@tanstack/react-router' + +export class Money { + constructor( + public readonly amount: bigint, + public readonly currency: string, + ) {} +} + +export const moneyAdapter = createSerializationAdapter({ + key: 'money', + test: (value): value is Money => value instanceof Money, + toSerializable: (money) => ({ + amount: money.amount, + currency: money.currency, + }), + fromSerializable: ({ amount, currency }) => new Money(amount, currency), +}) +``` + +Register the adapter in `src/start.ts`: + +```tsx +// src/start.ts +import { createStart } from '@tanstack/react-start' +import { moneyAdapter } from './serialization' + +export const startInstance = createStart(() => ({ + serializationAdapters: [moneyAdapter], +})) +``` + +The `key` uniquely identifies the adapter. The `test` function identifies values handled by the adapter. `toSerializable` converts the value to supported data, while `fromSerializable` reconstructs the original type. + ## Error Handling & Redirects Server functions can throw errors, redirects, and not-found responses that are handled automatically when called from route lifecycles or components using `useServerFn()`.