packages/client/skills/superjson/SKILL.md
npm install superjson
// server/trpc.ts
import { initTRPC } from '@trpc/server';
import superjson from 'superjson';
const t = initTRPC.create({
transformer: superjson,
});
export const router = t.router;
export const publicProcedure = t.procedure;
// client.ts
import { createTRPCClient, httpBatchLink } from '@trpc/client';
import superjson from 'superjson';
import type { AppRouter } from './server/trpc';
const client = createTRPCClient<AppRouter>({
links: [
httpBatchLink({
url: 'http://localhost:3000/trpc',
transformer: superjson,
}),
],
});
Now Date, Map, Set, BigInt, RegExp, undefined, and other non-JSON types survive the round trip.
import {
createTRPCClient,
httpBatchLink,
httpSubscriptionLink,
splitLink,
} from '@trpc/client';
import superjson from 'superjson';
import type { AppRouter } from './server/trpc';
const client = createTRPCClient<AppRouter>({
links: [
splitLink({
condition: (op) => op.type === 'subscription',
true: httpSubscriptionLink({
url: 'http://localhost:3000/trpc',
transformer: superjson,
}),
false: httpBatchLink({
url: 'http://localhost:3000/trpc',
transformer: superjson,
}),
}),
],
});
Every terminating link in every branch must have transformer: superjson.
import { createTRPCClient, createWSClient, wsLink } from '@trpc/client';
import superjson from 'superjson';
import type { AppRouter } from './server/trpc';
const wsClient = createWSClient({
url: 'ws://localhost:3000',
});
const client = createTRPCClient<AppRouter>({
links: [
wsLink<AppRouter>({
client: wsClient,
transformer: superjson,
}),
],
});
// server
import { z } from 'zod';
import { publicProcedure, router } from './trpc';
const appRouter = router({
getEvent: publicProcedure
.input(z.object({ id: z.string() }))
.query(({ input }) => {
return {
id: input.id,
name: 'Launch Party',
date: new Date('2025-01-01T00:00:00Z'),
};
}),
});
export type AppRouter = typeof appRouter;
// client
const event = await client.getEvent.query({ id: '1' });
console.log(event.date instanceof Date); // true
console.log(event.date.getFullYear()); // 2025
Without superjson, event.date would be a string like "2025-01-01T00:00:00.000Z".
Wrong:
// Server
const t = initTRPC.create({ transformer: superjson });
// Client
const client = createTRPCClient<AppRouter>({
links: [httpBatchLink({ url: 'http://localhost:3000/trpc' })],
});
Correct:
// Server
const t = initTRPC.create({ transformer: superjson });
// Client
const client = createTRPCClient<AppRouter>({
links: [
httpBatchLink({
url: 'http://localhost:3000/trpc',
transformer: superjson,
}),
],
});
Server encodes with superjson but client tries to parse raw JSON, causing "Unable to transform response" or garbled data.
Source: www/docs/server/data-transformers.md
The transformer option is on individual terminating links:
createTRPCClient<AppRouter>({
links: [
httpBatchLink({
url: 'http://localhost:3000/trpc',
transformer: superjson,
}),
],
});
In v11, transformer was moved from the client constructor to individual links. Passing it to createTRPCClient throws a TypeError.
Source: packages/client/src/internals/TRPCUntypedClient.ts
Wrong:
splitLink({
condition: (op) => op.type === 'subscription',
true: httpSubscriptionLink({
url: 'http://localhost:3000/trpc',
// missing transformer!
}),
false: httpBatchLink({
url: 'http://localhost:3000/trpc',
transformer: superjson,
}),
});
Correct:
splitLink({
condition: (op) => op.type === 'subscription',
true: httpSubscriptionLink({
url: 'http://localhost:3000/trpc',
transformer: superjson,
}),
false: httpBatchLink({
url: 'http://localhost:3000/trpc',
transformer: superjson,
}),
});
Every terminating link must have the same transformer. A missing transformer on one branch causes deserialization failures only for operations routed through that branch.
Source: www/docs/server/data-transformers.md
Wrong:
// Server -- no transformer
const t = initTRPC.create();
// Client
httpBatchLink({ url, transformer: superjson });
Correct:
// Server
const t = initTRPC.create({ transformer: superjson });
// Client
httpBatchLink({ url, transformer: superjson });
The transformer must be configured on both initTRPC.create() and every client link. Client-only transformer corrupts the request encoding because the server expects plain JSON.
Source: www/docs/server/data-transformers.md
client-setup -- create the tRPC client and configure linkslinks -- detailed options for each link type including transformerserver-setup -- initTRPC.create() where the server transformer is configured