CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-trpc--next

Next.js integration for tRPC that enables end-to-end type-safe APIs with enhanced server-side rendering and static site generation capabilities.

Pending
Overview
Eval results
Files

cache-links.mddocs/

Cache Links

Specialized tRPC links for Next.js caching integration with cache tags and revalidation support.

Capabilities

Next.js Cache Link

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>
  );
}

Next.js HTTP Link

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],
    };
  },
});

Cache Tag Management

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");
}

Per-Request Cache Control

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>;
}

Advanced Cache Patterns

Hierarchical Cache Invalidation

// 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.*");
}

Cache Warming

// 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 });
}

Conditional Caching

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;
  },
});

Background Revalidation

// 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");
}

Error Handling

Cache Link Error Recovery

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>;
  }
}

Cache Monitoring

// 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,
});

Types

// 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

docs

app-router.md

cache-links.md

index.md

pages-router.md

server-actions.md

ssr.md

tile.json