0
# Server-Side Helpers
1
2
Functions for server-side rendering, static generation, and React Server Components support. These utilities enable data prefetching and hydration in Next.js and other server-side rendering frameworks.
3
4
## Capabilities
5
6
### createServerSideHelpers
7
8
Creates a set of utilities for server-side data fetching and cache management.
9
10
```typescript { .api }
11
/**
12
* Creates server-side helpers for SSG, SSR, and data prefetching
13
* @param opts - Configuration options with router/client and query client
14
* @returns Object with server-side utility functions and dehydration
15
*/
16
function createServerSideHelpers<TRouter extends AnyRouter>(
17
opts: CreateServerSideHelpersOptions<TRouter>
18
): ServerSideHelpers<TRouter>;
19
20
type CreateServerSideHelpersOptions<TRouter extends AnyRouter> =
21
CreateTRPCReactQueryClientConfig & (
22
| CreateSSGHelpersInternal<TRouter>
23
| CreateSSGHelpersExternal<TRouter>
24
);
25
26
interface CreateSSGHelpersInternal<TRouter extends AnyRouter> {
27
/** tRPC router instance */
28
router: TRouter;
29
/** Router context for procedure calls */
30
ctx: inferRouterContext<TRouter>;
31
/** Data transformer configuration */
32
transformer?: TransformerOptions;
33
}
34
35
interface CreateSSGHelpersExternal<TRouter extends AnyRouter> {
36
/** tRPC client instance */
37
client: TRPCClient<TRouter> | TRPCUntypedClient<TRouter>;
38
}
39
40
interface ServerSideHelpers<TRouter> {
41
/** React Query client instance */
42
queryClient: QueryClient;
43
/** Dehydrate query cache for client-side hydration */
44
dehydrate: (opts?: DehydrateOptions) => DehydratedState;
45
46
// Procedure-specific methods are generated based on router structure
47
// Each query procedure gets: fetch, fetchInfinite, prefetch, prefetchInfinite, queryOptions, infiniteQueryOptions
48
}
49
```
50
51
**Usage Examples:**
52
53
```typescript
54
// With router and context (internal)
55
import { createServerSideHelpers } from "@trpc/react-query/server";
56
import { createContext } from "./context";
57
import { appRouter } from "./router";
58
59
export const ssg = createServerSideHelpers({
60
router: appRouter,
61
ctx: await createContext(),
62
transformer: superjson,
63
});
64
65
// With client (external)
66
export const ssg = createServerSideHelpers({
67
client: trpcClient,
68
queryClient: new QueryClient(),
69
});
70
```
71
72
### Server-Side Data Fetching
73
74
Fetch data on the server before rendering components.
75
76
```typescript { .api }
77
/**
78
* Fetch query data on the server
79
* @param input - Input parameters for the procedure
80
* @param opts - Server-side fetch options
81
* @returns Promise resolving to the fetched data
82
*/
83
procedure.fetch(
84
input: TInput,
85
opts?: TRPCFetchQueryOptions<TOutput, TError>
86
): Promise<TOutput>;
87
88
/**
89
* Fetch infinite query data on the server
90
* @param input - Input parameters for the infinite query
91
* @param opts - Server-side fetch infinite options
92
* @returns Promise resolving to the fetched infinite data
93
*/
94
procedure.fetchInfinite(
95
input: GetInfiniteQueryInput<TInput>,
96
opts: TRPCFetchInfiniteQueryOptions<TInput, TOutput, TError>
97
): Promise<InfiniteData<TOutput>>;
98
```
99
100
**Usage Examples:**
101
102
```typescript
103
// Next.js getServerSideProps
104
export async function getServerSideProps(context: GetServerSidePropsContext) {
105
const ssg = createServerSideHelpers({
106
router: appRouter,
107
ctx: await createContext(context),
108
});
109
110
// Fetch user data on the server
111
const user = await ssg.user.get.fetch({ id: 1 });
112
113
// Fetch paginated posts
114
const posts = await ssg.posts.list.fetchInfinite(
115
{ limit: 10 },
116
{ initialCursor: null }
117
);
118
119
return {
120
props: {
121
user,
122
posts,
123
trpcState: ssg.dehydrate(),
124
},
125
};
126
}
127
128
// Next.js getStaticProps
129
export async function getStaticProps() {
130
const ssg = createServerSideHelpers({
131
router: appRouter,
132
ctx: await createContext(),
133
});
134
135
try {
136
await ssg.posts.list.fetch({ limit: 100 });
137
} catch (error) {
138
console.error("Failed to fetch posts:", error);
139
}
140
141
return {
142
props: {
143
trpcState: ssg.dehydrate(),
144
},
145
revalidate: 60, // Revalidate every minute
146
};
147
}
148
```
149
150
### Server-Side Prefetching
151
152
Prefetch data on the server to populate the query cache.
153
154
```typescript { .api }
155
/**
156
* Prefetch query data on the server
157
* @param input - Input parameters for the procedure
158
* @param opts - Server-side prefetch options
159
* @returns Promise that resolves when prefetch is complete
160
*/
161
procedure.prefetch(
162
input: TInput,
163
opts?: TRPCFetchQueryOptions<TOutput, TError>
164
): Promise<void>;
165
166
/**
167
* Prefetch infinite query data on the server
168
* @param input - Input parameters for the infinite query
169
* @param opts - Server-side prefetch infinite options
170
* @returns Promise that resolves when prefetch is complete
171
*/
172
procedure.prefetchInfinite(
173
input: GetInfiniteQueryInput<TInput>,
174
opts: TRPCFetchInfiniteQueryOptions<TInput, TOutput, TError>
175
): Promise<void>;
176
```
177
178
**Usage Examples:**
179
180
```typescript
181
// Prefetch multiple queries
182
export async function getServerSideProps(context: GetServerSidePropsContext) {
183
const ssg = createServerSideHelpers({
184
router: appRouter,
185
ctx: await createContext(context),
186
});
187
188
// Prefetch multiple related queries
189
await Promise.all([
190
ssg.user.get.prefetch({ id: 1 }),
191
ssg.user.posts.prefetch({ userId: 1 }),
192
ssg.posts.list.prefetchInfinite(
193
{ limit: 10 },
194
{ initialCursor: null }
195
),
196
]);
197
198
return {
199
props: {
200
trpcState: ssg.dehydrate(),
201
},
202
};
203
}
204
205
// Conditional prefetching
206
export async function getStaticProps({ params }: GetStaticPropsContext) {
207
const ssg = createServerSideHelpers({
208
router: appRouter,
209
ctx: await createContext(),
210
});
211
212
const userId = Number(params?.userId);
213
214
if (userId) {
215
// Only prefetch if userId is valid
216
await ssg.user.get.prefetch({ id: userId });
217
}
218
219
return {
220
props: {
221
trpcState: ssg.dehydrate(),
222
userId,
223
},
224
};
225
}
226
```
227
228
### Query Options Generation
229
230
Generate React Query options for server-side use.
231
232
```typescript { .api }
233
/**
234
* Generate query options for React Query
235
* @param input - Input parameters for the procedure
236
* @param opts - Query options configuration
237
* @returns React Query options object
238
*/
239
procedure.queryOptions(
240
input: TInput,
241
opts?: TRPCQueryOptions<TOutput, TError>
242
): DefinedTRPCQueryOptionsOut<TOutput, TOutput, TError>;
243
244
/**
245
* Generate infinite query options for React Query
246
* @param input - Input parameters for the infinite query
247
* @param opts - Infinite query options configuration
248
* @returns React Query infinite options object
249
*/
250
procedure.infiniteQueryOptions(
251
input: GetInfiniteQueryInput<TInput>,
252
opts?: TRPCInfiniteQueryOptions<TInput, TOutput, TError>
253
): DefinedTRPCInfiniteQueryOptionsOut<TInput, TOutput, TError>;
254
```
255
256
**Usage Examples:**
257
258
```typescript
259
// Generate query options for custom usage
260
function CustomServerSideLogic() {
261
const ssg = createServerSideHelpers({
262
router: appRouter,
263
ctx: createContext(),
264
});
265
266
// Generate query options
267
const userQueryOptions = ssg.user.get.queryOptions({ id: 1 });
268
const postsQueryOptions = ssg.posts.list.infiniteQueryOptions(
269
{ limit: 10 },
270
{ initialCursor: null }
271
);
272
273
// Use with React Query directly
274
const queryClient = new QueryClient();
275
276
// Prefetch using generated options
277
await queryClient.prefetchQuery(userQueryOptions);
278
await queryClient.prefetchInfiniteQuery(postsQueryOptions);
279
280
return {
281
userQueryOptions,
282
postsQueryOptions,
283
};
284
}
285
```
286
287
### Dehydration and Hydration
288
289
Serialize server-side cache state for client-side hydration.
290
291
```typescript { .api }
292
/**
293
* Dehydrate query cache for client-side hydration
294
* @param opts - Dehydration options
295
* @returns Serialized state for client hydration
296
*/
297
dehydrate(opts?: DehydrateOptions): DehydratedState;
298
299
interface DehydrateOptions {
300
shouldDehydrateQuery?: (query: Query) => boolean;
301
shouldDehydrateMutation?: (mutation: Mutation) => boolean;
302
serializeData?: (data: unknown) => unknown;
303
}
304
```
305
306
**Usage Examples:**
307
308
```typescript
309
// Basic dehydration
310
export async function getServerSideProps() {
311
const ssg = createServerSideHelpers({
312
router: appRouter,
313
ctx: await createContext(),
314
});
315
316
await ssg.user.list.prefetch();
317
318
return {
319
props: {
320
trpcState: ssg.dehydrate(),
321
},
322
};
323
}
324
325
// Custom dehydration options
326
export async function getServerSideProps() {
327
const ssg = createServerSideHelpers({
328
router: appRouter,
329
ctx: await createContext(),
330
});
331
332
await ssg.user.list.prefetch();
333
334
return {
335
props: {
336
trpcState: ssg.dehydrate({
337
shouldDehydrateQuery: (query) => {
338
// Only dehydrate successful queries
339
return query.state.status === 'success';
340
},
341
}),
342
},
343
};
344
}
345
346
// Client-side hydration
347
function MyApp({ Component, pageProps }: AppProps) {
348
const [queryClient] = useState(() => new QueryClient());
349
const [trpcClient] = useState(() => trpc.createClient({
350
url: "http://localhost:3000/api/trpc",
351
}));
352
353
return (
354
<trpc.Provider client={trpcClient} queryClient={queryClient}>
355
<QueryClientProvider client={queryClient}>
356
<Hydrate state={pageProps.trpcState}>
357
<Component {...pageProps} />
358
</Hydrate>
359
</QueryClientProvider>
360
</trpc.Provider>
361
);
362
}
363
```
364
365
366
## Common Patterns
367
368
### Error Handling in SSR
369
370
```typescript
371
export async function getServerSideProps(context: GetServerSidePropsContext) {
372
const ssg = createServerSideHelpers({
373
router: appRouter,
374
ctx: await createContext(context),
375
});
376
377
try {
378
await ssg.user.get.fetch({ id: 1 });
379
} catch (error) {
380
console.error("Failed to fetch user:", error);
381
382
// Handle specific errors
383
if (error.code === "NOT_FOUND") {
384
return { notFound: true };
385
}
386
387
// Return error props
388
return {
389
props: {
390
error: error.message,
391
trpcState: ssg.dehydrate(),
392
},
393
};
394
}
395
396
return {
397
props: {
398
trpcState: ssg.dehydrate(),
399
},
400
};
401
}
402
```
403
404
### Conditional Server-Side Logic
405
406
```typescript
407
export async function getStaticProps({ params }: GetStaticPropsContext) {
408
const ssg = createServerSideHelpers({
409
router: appRouter,
410
ctx: await createContext(),
411
});
412
413
const userId = params?.userId as string;
414
415
if (userId && !isNaN(Number(userId))) {
416
// Only fetch if userId is valid
417
try {
418
await ssg.user.get.fetch({ id: Number(userId) });
419
await ssg.user.posts.prefetch({ userId: Number(userId) });
420
} catch (error) {
421
console.warn(`Failed to fetch data for user ${userId}:`, error);
422
}
423
}
424
425
return {
426
props: {
427
trpcState: ssg.dehydrate(),
428
userId: userId || null,
429
},
430
};
431
}
432
```
433
434
### Performance Optimization
435
436
```typescript
437
export async function getServerSideProps() {
438
const ssg = createServerSideHelpers({
439
router: appRouter,
440
ctx: await createContext(),
441
});
442
443
// Fetch critical data first
444
const criticalData = await ssg.app.config.fetch();
445
446
// Prefetch non-critical data in parallel
447
await Promise.allSettled([
448
ssg.user.recommendations.prefetch(),
449
ssg.posts.trending.prefetch(),
450
ssg.notifications.recent.prefetch(),
451
]);
452
453
return {
454
props: {
455
criticalData,
456
trpcState: ssg.dehydrate({
457
shouldDehydrateQuery: (query) => {
458
// Only dehydrate successful queries to reduce payload size
459
return query.state.status === 'success';
460
},
461
}),
462
},
463
};
464
}
465
```
466
467
### Custom Context Creation
468
469
```typescript
470
async function createSSRContext(context: GetServerSidePropsContext) {
471
const session = await getSession(context.req);
472
473
return {
474
session,
475
req: context.req,
476
res: context.res,
477
};
478
}
479
480
export async function getServerSideProps(context: GetServerSidePropsContext) {
481
const ssg = createServerSideHelpers({
482
router: appRouter,
483
ctx: await createSSRContext(context),
484
});
485
486
if (session?.user) {
487
await ssg.user.profile.prefetch({ id: session.user.id });
488
}
489
490
return {
491
props: {
492
trpcState: ssg.dehydrate(),
493
session,
494
},
495
};
496
}
497
```