Next.js integration for tRPC that enables end-to-end type-safe APIs with enhanced server-side rendering and static site generation capabilities.
—
Specialized tRPC links for Next.js caching integration with cache tags and revalidation support.
tRPC link that integrates with Next.js unstable_cache for server-side caching with tag-based revalidation.
/**
* tRPC link that integrates with Next.js unstable_cache for server-side caching
* @param opts - Configuration options for cache behavior and context creation
* @returns tRPC link with Next.js caching integration
*/
function experimental_nextCacheLink<TRouter extends AnyRouter>(
opts: NextCacheLinkOptions<TRouter>
): TRPCLink<TRouter>;
interface NextCacheLinkOptions<TRouter extends AnyRouter> extends TransformerOptions<inferClientTypes<TRouter>> {
/** tRPC router instance */
router: TRouter;
/** Function to create context for each request */
createContext: () => Promise<inferRouterContext<TRouter>>;
/** How many seconds the cache should hold before revalidating (default: false) */
revalidate?: number | false;
}Usage Examples:
import { experimental_nextCacheLink } from "@trpc/next/app-dir/links/nextCache";
import { appRouter } from "./api/root";
// Basic cache link configuration
const cacheLink = experimental_nextCacheLink({
router: appRouter,
createContext: async () => ({
// Your context creation logic
userId: getUserId(),
db: getDatabase(),
}),
revalidate: 300, // Cache for 5 minutes
});
// Use in App Router server
const trpc = experimental_createTRPCNextAppDirServer({
config() {
return {
links: [cacheLink],
};
},
});
// Server component with caching
async function ProductList() {
// This query result will be cached for 5 minutes
const products = await trpc.product.list.query();
return (
<div>
{products.map(product => (
<div key={product.id}>{product.name}</div>
))}
</div>
);
}HTTP link with Next.js fetch caching and revalidation support for client-side requests.
/**
* HTTP link with Next.js fetch caching and revalidation support
* @param opts - Configuration options for HTTP requests and caching
* @returns tRPC link with Next.js HTTP caching
*/
function experimental_nextHttpLink<TRouter extends AnyRouter>(
opts: NextLinkSingleOptions<TRouter['_def']['_config']['$types']> | NextLinkBatchOptions<TRouter['_def']['_config']['$types']>
): TRPCLink<TRouter>;
interface NextLinkBaseOptions {
/** How many seconds the cache should hold before revalidating (default: false) */
revalidate?: number | false;
/** Whether to batch multiple requests (default: false) */
batch?: boolean;
}
type NextLinkSingleOptions<TRoot extends AnyRootTypes> = NextLinkBaseOptions &
Omit<HTTPLinkOptions<TRoot>, 'fetch'> & {
batch?: false;
};
type NextLinkBatchOptions<TRoot extends AnyRootTypes> = NextLinkBaseOptions &
Omit<HTTPBatchLinkOptions<TRoot>, 'fetch'> & {
batch: true;
};Usage Examples:
import { experimental_nextHttpLink } from "@trpc/next/app-dir/links/nextHttp";
// Single request configuration
const httpLink = experimental_nextHttpLink({
url: "/api/trpc",
revalidate: 60, // Cache for 1 minute
batch: false,
});
// Batched requests configuration
const batchedHttpLink = experimental_nextHttpLink({
url: "/api/trpc",
revalidate: 120, // Cache for 2 minutes
batch: true,
maxBatchSize: 10,
});
// Use in client configuration
const trpc = experimental_createTRPCNextAppDirClient({
config() {
return {
links: [batchedHttpLink],
};
},
});Both cache links use cache tags for fine-grained cache invalidation.
/**
* Generates cache tags for procedures based on path and input
* @param procedurePath - tRPC procedure path (e.g., "user.getProfile")
* @param input - Input parameters for the procedure
* @returns Cache tag string for Next.js caching
*/
function generateCacheTag(procedurePath: string, input: any): string;Usage Examples:
// Cache tags are automatically generated
// For query: trpc.user.getProfile.query({ userId: "123" })
// Generated tag: "user.getProfile?input={\"userId\":\"123\"}"
// For query without input: trpc.posts.list.query()
// Generated tag: "posts.list"
// Manual revalidation using cache tags
import { revalidateTag } from "next/cache";
async function invalidateUserCache(userId: string) {
// Revalidate specific user profile
revalidateTag(`user.getProfile?input=${JSON.stringify({ userId })}`);
// Or revalidate all user queries
revalidateTag("user.getProfile");
}Override global cache settings on a per-request basis.
// Server component with custom cache control
async function DynamicContent() {
// Override global revalidate setting
const data = await trpc.content.get.query(
{ slug: "homepage" },
{
context: {
revalidate: 30 // Cache for 30 seconds instead of default
}
}
);
return <div>{data.content}</div>;
}
// Disable caching for specific request
async function RealTimeData() {
const data = await trpc.analytics.current.query(
{},
{
context: {
revalidate: false // Disable caching
}
}
);
return <div>Live count: {data.count}</div>;
}// Organize cache tags hierarchically
const userCacheLink = experimental_nextCacheLink({
router: appRouter,
createContext: async () => ({}),
revalidate: 600,
});
// When a user is updated, invalidate all related caches
async function updateUser(userId: string, updates: UserUpdate) {
// Update user in database
await db.user.update(userId, updates);
// Invalidate all user-related caches
revalidateTag(`user.getProfile?input=${JSON.stringify({ userId })}`);
revalidateTag(`user.getPosts?input=${JSON.stringify({ userId })}`);
revalidateTag(`user.getSettings?input=${JSON.stringify({ userId })}`);
// Or use a broader invalidation pattern
revalidateTag("user.*");
}// Pre-warm cache for common queries
async function warmCache() {
const commonQueries = [
trpc.posts.popular.query(),
trpc.categories.list.query(),
trpc.settings.public.query(),
];
// Execute queries to populate cache
await Promise.all(commonQueries);
}
// Use in page or API route
export async function GET() {
await warmCache();
return new Response("Cache warmed", { status: 200 });
}const conditionalCacheLink = experimental_nextCacheLink({
router: appRouter,
createContext: async () => {
const user = await getCurrentUser();
return { user };
},
revalidate: (context) => {
// Cache authenticated requests longer
return context.user ? 300 : 60;
},
});// Set up background revalidation
async function setupBackgroundRevalidation() {
setInterval(async () => {
// Revalidate frequently changing data
revalidateTag("analytics.current");
revalidateTag("notifications.unread");
}, 30000); // Every 30 seconds
}
// Webhook-triggered revalidation
export async function POST(request: Request) {
const { type, entityId } = await request.json();
switch (type) {
case "user.updated":
revalidateTag(`user.getProfile?input=${JSON.stringify({ userId: entityId })}`);
break;
case "post.published":
revalidateTag("posts.list");
revalidateTag("posts.popular");
break;
}
return new Response("OK");
}const robustCacheLink = experimental_nextCacheLink({
router: appRouter,
createContext: async () => {
try {
return await createContext();
} catch (error) {
console.error("Context creation failed:", error);
// Return minimal context on error
return {};
}
},
revalidate: 300,
});
// Handle cache failures gracefully
async function CachedComponent() {
try {
const data = await trpc.data.get.query();
return <div>{data.content}</div>;
} catch (error) {
console.error("Cache query failed:", error);
// Fallback to uncached query or default content
return <div>Content temporarily unavailable</div>;
}
}// Monitor cache performance
const monitoredCacheLink = experimental_nextCacheLink({
router: appRouter,
createContext: async () => {
const start = Date.now();
const context = await createContext();
const duration = Date.now() - start;
// Log slow context creation
if (duration > 100) {
console.warn(`Slow context creation: ${duration}ms`);
}
return context;
},
revalidate: 300,
});// Cache link configuration for Next.js unstable_cache
interface NextCacheLinkOptions<TRouter extends AnyRouter> extends TransformerOptions<inferClientTypes<TRouter>> {
router: TRouter;
createContext: () => Promise<inferRouterContext<TRouter>>;
revalidate?: number | false;
}
// HTTP link options for Next.js fetch caching
interface NextLinkBaseOptions {
revalidate?: number | false;
batch?: boolean;
}
// tRPC link type
interface TRPCLink<TRouter extends AnyRouter> {
(runtime: ClientRuntimeConfig): (ctx: OperationContext<TRouter>) => Observable<OperationResult<any>>;
}
// Router context inference
type inferRouterContext<TRouter extends AnyRouter> = TRouter['_def']['_config']['$types']['ctx'];
// Client types inference
type inferClientTypes<TRouter extends AnyRouter> = TRouter['_def']['_config']['$types'];Install with Tessl CLI
npx tessl i tessl/npm-trpc--next