0
# Cache Links
1
2
Specialized tRPC links for Next.js caching integration with cache tags and revalidation support.
3
4
## Capabilities
5
6
### Next.js Cache Link
7
8
tRPC link that integrates with Next.js `unstable_cache` for server-side caching with tag-based revalidation.
9
10
```typescript { .api }
11
/**
12
* tRPC link that integrates with Next.js unstable_cache for server-side caching
13
* @param opts - Configuration options for cache behavior and context creation
14
* @returns tRPC link with Next.js caching integration
15
*/
16
function experimental_nextCacheLink<TRouter extends AnyRouter>(
17
opts: NextCacheLinkOptions<TRouter>
18
): TRPCLink<TRouter>;
19
20
interface NextCacheLinkOptions<TRouter extends AnyRouter> extends TransformerOptions<inferClientTypes<TRouter>> {
21
/** tRPC router instance */
22
router: TRouter;
23
/** Function to create context for each request */
24
createContext: () => Promise<inferRouterContext<TRouter>>;
25
/** How many seconds the cache should hold before revalidating (default: false) */
26
revalidate?: number | false;
27
}
28
```
29
30
**Usage Examples:**
31
32
```typescript
33
import { experimental_nextCacheLink } from "@trpc/next/app-dir/links/nextCache";
34
import { appRouter } from "./api/root";
35
36
// Basic cache link configuration
37
const cacheLink = experimental_nextCacheLink({
38
router: appRouter,
39
createContext: async () => ({
40
// Your context creation logic
41
userId: getUserId(),
42
db: getDatabase(),
43
}),
44
revalidate: 300, // Cache for 5 minutes
45
});
46
47
// Use in App Router server
48
const trpc = experimental_createTRPCNextAppDirServer({
49
config() {
50
return {
51
links: [cacheLink],
52
};
53
},
54
});
55
56
// Server component with caching
57
async function ProductList() {
58
// This query result will be cached for 5 minutes
59
const products = await trpc.product.list.query();
60
return (
61
<div>
62
{products.map(product => (
63
<div key={product.id}>{product.name}</div>
64
))}
65
</div>
66
);
67
}
68
```
69
70
### Next.js HTTP Link
71
72
HTTP link with Next.js fetch caching and revalidation support for client-side requests.
73
74
```typescript { .api }
75
/**
76
* HTTP link with Next.js fetch caching and revalidation support
77
* @param opts - Configuration options for HTTP requests and caching
78
* @returns tRPC link with Next.js HTTP caching
79
*/
80
function experimental_nextHttpLink<TRouter extends AnyRouter>(
81
opts: NextLinkSingleOptions<TRouter['_def']['_config']['$types']> | NextLinkBatchOptions<TRouter['_def']['_config']['$types']>
82
): TRPCLink<TRouter>;
83
84
interface NextLinkBaseOptions {
85
/** How many seconds the cache should hold before revalidating (default: false) */
86
revalidate?: number | false;
87
/** Whether to batch multiple requests (default: false) */
88
batch?: boolean;
89
}
90
91
type NextLinkSingleOptions<TRoot extends AnyRootTypes> = NextLinkBaseOptions &
92
Omit<HTTPLinkOptions<TRoot>, 'fetch'> & {
93
batch?: false;
94
};
95
96
type NextLinkBatchOptions<TRoot extends AnyRootTypes> = NextLinkBaseOptions &
97
Omit<HTTPBatchLinkOptions<TRoot>, 'fetch'> & {
98
batch: true;
99
};
100
```
101
102
**Usage Examples:**
103
104
```typescript
105
import { experimental_nextHttpLink } from "@trpc/next/app-dir/links/nextHttp";
106
107
// Single request configuration
108
const httpLink = experimental_nextHttpLink({
109
url: "/api/trpc",
110
revalidate: 60, // Cache for 1 minute
111
batch: false,
112
});
113
114
// Batched requests configuration
115
const batchedHttpLink = experimental_nextHttpLink({
116
url: "/api/trpc",
117
revalidate: 120, // Cache for 2 minutes
118
batch: true,
119
maxBatchSize: 10,
120
});
121
122
// Use in client configuration
123
const trpc = experimental_createTRPCNextAppDirClient({
124
config() {
125
return {
126
links: [batchedHttpLink],
127
};
128
},
129
});
130
```
131
132
### Cache Tag Management
133
134
Both cache links use cache tags for fine-grained cache invalidation.
135
136
```typescript { .api }
137
/**
138
* Generates cache tags for procedures based on path and input
139
* @param procedurePath - tRPC procedure path (e.g., "user.getProfile")
140
* @param input - Input parameters for the procedure
141
* @returns Cache tag string for Next.js caching
142
*/
143
function generateCacheTag(procedurePath: string, input: any): string;
144
```
145
146
**Usage Examples:**
147
148
```typescript
149
// Cache tags are automatically generated
150
// For query: trpc.user.getProfile.query({ userId: "123" })
151
// Generated tag: "user.getProfile?input={\"userId\":\"123\"}"
152
153
// For query without input: trpc.posts.list.query()
154
// Generated tag: "posts.list"
155
156
// Manual revalidation using cache tags
157
import { revalidateTag } from "next/cache";
158
159
async function invalidateUserCache(userId: string) {
160
// Revalidate specific user profile
161
revalidateTag(`user.getProfile?input=${JSON.stringify({ userId })}`);
162
163
// Or revalidate all user queries
164
revalidateTag("user.getProfile");
165
}
166
```
167
168
### Per-Request Cache Control
169
170
Override global cache settings on a per-request basis.
171
172
```typescript
173
// Server component with custom cache control
174
async function DynamicContent() {
175
// Override global revalidate setting
176
const data = await trpc.content.get.query(
177
{ slug: "homepage" },
178
{
179
context: {
180
revalidate: 30 // Cache for 30 seconds instead of default
181
}
182
}
183
);
184
185
return <div>{data.content}</div>;
186
}
187
188
// Disable caching for specific request
189
async function RealTimeData() {
190
const data = await trpc.analytics.current.query(
191
{},
192
{
193
context: {
194
revalidate: false // Disable caching
195
}
196
}
197
);
198
199
return <div>Live count: {data.count}</div>;
200
}
201
```
202
203
## Advanced Cache Patterns
204
205
### Hierarchical Cache Invalidation
206
207
```typescript
208
// Organize cache tags hierarchically
209
const userCacheLink = experimental_nextCacheLink({
210
router: appRouter,
211
createContext: async () => ({}),
212
revalidate: 600,
213
});
214
215
// When a user is updated, invalidate all related caches
216
async function updateUser(userId: string, updates: UserUpdate) {
217
// Update user in database
218
await db.user.update(userId, updates);
219
220
// Invalidate all user-related caches
221
revalidateTag(`user.getProfile?input=${JSON.stringify({ userId })}`);
222
revalidateTag(`user.getPosts?input=${JSON.stringify({ userId })}`);
223
revalidateTag(`user.getSettings?input=${JSON.stringify({ userId })}`);
224
225
// Or use a broader invalidation pattern
226
revalidateTag("user.*");
227
}
228
```
229
230
### Cache Warming
231
232
```typescript
233
// Pre-warm cache for common queries
234
async function warmCache() {
235
const commonQueries = [
236
trpc.posts.popular.query(),
237
trpc.categories.list.query(),
238
trpc.settings.public.query(),
239
];
240
241
// Execute queries to populate cache
242
await Promise.all(commonQueries);
243
}
244
245
// Use in page or API route
246
export async function GET() {
247
await warmCache();
248
return new Response("Cache warmed", { status: 200 });
249
}
250
```
251
252
### Conditional Caching
253
254
```typescript
255
const conditionalCacheLink = experimental_nextCacheLink({
256
router: appRouter,
257
createContext: async () => {
258
const user = await getCurrentUser();
259
return { user };
260
},
261
revalidate: (context) => {
262
// Cache authenticated requests longer
263
return context.user ? 300 : 60;
264
},
265
});
266
```
267
268
### Background Revalidation
269
270
```typescript
271
// Set up background revalidation
272
async function setupBackgroundRevalidation() {
273
setInterval(async () => {
274
// Revalidate frequently changing data
275
revalidateTag("analytics.current");
276
revalidateTag("notifications.unread");
277
}, 30000); // Every 30 seconds
278
}
279
280
// Webhook-triggered revalidation
281
export async function POST(request: Request) {
282
const { type, entityId } = await request.json();
283
284
switch (type) {
285
case "user.updated":
286
revalidateTag(`user.getProfile?input=${JSON.stringify({ userId: entityId })}`);
287
break;
288
case "post.published":
289
revalidateTag("posts.list");
290
revalidateTag("posts.popular");
291
break;
292
}
293
294
return new Response("OK");
295
}
296
```
297
298
## Error Handling
299
300
### Cache Link Error Recovery
301
302
```typescript
303
const robustCacheLink = experimental_nextCacheLink({
304
router: appRouter,
305
createContext: async () => {
306
try {
307
return await createContext();
308
} catch (error) {
309
console.error("Context creation failed:", error);
310
// Return minimal context on error
311
return {};
312
}
313
},
314
revalidate: 300,
315
});
316
317
// Handle cache failures gracefully
318
async function CachedComponent() {
319
try {
320
const data = await trpc.data.get.query();
321
return <div>{data.content}</div>;
322
} catch (error) {
323
console.error("Cache query failed:", error);
324
// Fallback to uncached query or default content
325
return <div>Content temporarily unavailable</div>;
326
}
327
}
328
```
329
330
### Cache Monitoring
331
332
```typescript
333
// Monitor cache performance
334
const monitoredCacheLink = experimental_nextCacheLink({
335
router: appRouter,
336
createContext: async () => {
337
const start = Date.now();
338
const context = await createContext();
339
const duration = Date.now() - start;
340
341
// Log slow context creation
342
if (duration > 100) {
343
console.warn(`Slow context creation: ${duration}ms`);
344
}
345
346
return context;
347
},
348
revalidate: 300,
349
});
350
```
351
352
## Types
353
354
```typescript { .api }
355
// Cache link configuration for Next.js unstable_cache
356
interface NextCacheLinkOptions<TRouter extends AnyRouter> extends TransformerOptions<inferClientTypes<TRouter>> {
357
router: TRouter;
358
createContext: () => Promise<inferRouterContext<TRouter>>;
359
revalidate?: number | false;
360
}
361
362
// HTTP link options for Next.js fetch caching
363
interface NextLinkBaseOptions {
364
revalidate?: number | false;
365
batch?: boolean;
366
}
367
368
// tRPC link type
369
interface TRPCLink<TRouter extends AnyRouter> {
370
(runtime: ClientRuntimeConfig): (ctx: OperationContext<TRouter>) => Observable<OperationResult<any>>;
371
}
372
373
// Router context inference
374
type inferRouterContext<TRouter extends AnyRouter> = TRouter['_def']['_config']['$types']['ctx'];
375
376
// Client types inference
377
type inferClientTypes<TRouter extends AnyRouter> = TRouter['_def']['_config']['$types'];
378
```