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

app-router.mddocs/

App Router Support

Experimental features for Next.js App Router including server components, server actions, and advanced caching.

Capabilities

App Router Client

Client-side utilities for Next.js App Router with built-in caching and query management.

/**
 * Creates tRPC client specifically for Next.js App Router with caching support
 * @param opts - Configuration options for App Router client
 * @returns tRPC client instance with App Router optimizations
 */
function experimental_createTRPCNextAppDirClient<TRouter extends AnyRouter>(
  opts: CreateTRPCNextAppRouterOptions<TRouter>
): TRPCClient<TRouter>;

interface CreateTRPCNextAppRouterOptions<TRouter extends AnyRouter> {
  /** Function that returns tRPC client configuration */
  config: () => CreateTRPCClientOptions<TRouter>;
}

Usage Examples:

import { experimental_createTRPCNextAppDirClient } from "@trpc/next/app-dir/client";
import { httpBatchLink } from "@trpc/client";
import type { AppRouter } from "./api/trpc";

// Create App Router client
const trpc = experimental_createTRPCNextAppDirClient<AppRouter>({
  config() {
    return {
      links: [
        httpBatchLink({
          url: "/api/trpc",
        }),
      ],
    };
  },
});

// Use in client components
async function UserProfile() {
  const user = await trpc.user.getProfile.query({ userId: "123" });
  return <div>Hello, {user.name}!</div>;
}

App Router Server

Server-side utilities for Next.js App Router with revalidation and caching support.

/**
 * Creates server-side tRPC utilities for Next.js App Router
 * @param opts - Configuration options for App Router server
 * @returns Decorated router with server-side methods including revalidation
 */
function experimental_createTRPCNextAppDirServer<TRouter extends AnyRouter>(
  opts: CreateTRPCNextAppRouterOptions<TRouter>
): NextAppDirDecorateRouterRecord<TRouter['_def']['_config']['$types'], TRouter['_def']['record']>;

type NextAppDirDecorateRouterRecord<TRoot extends AnyRootTypes, TRecord extends RouterRecord> = {
  [TKey in keyof TRecord]: TRecord[TKey] extends infer $Value
    ? $Value extends AnyProcedure
      ? DecorateProcedureServer<$Value['_def']['type'], {
          input: inferProcedureInput<$Value>;
          output: inferTransformedProcedureOutput<TRoot, $Value>;
          errorShape: TRoot['errorShape'];
          transformer: TRoot['transformer'];
        }>
      : $Value extends RouterRecord
        ? NextAppDirDecorateRouterRecord<TRoot, $Value>
        : never
    : never;
};

Usage Examples:

import { experimental_createTRPCNextAppDirServer } from "@trpc/next/app-dir/server";
import { experimental_nextCacheLink } from "@trpc/next/app-dir/links/nextCache";
import { appRouter } from "./api/root";

// Create server instance
const trpc = experimental_createTRPCNextAppDirServer<typeof appRouter>({
  config() {
    return {
      links: [
        experimental_nextCacheLink({
          router: appRouter,
          createContext: async () => ({}),
          revalidate: 60, // Cache for 60 seconds
        }),
      ],
    };
  },
});

// Use in server components
async function UserList() {
  const users = await trpc.user.list.query();
  return (
    <div>
      {users.map(user => (
        <div key={user.id}>{user.name}</div>
      ))}
    </div>
  );
}

// Revalidate cache
async function RefreshUsers() {
  await trpc.user.list.revalidate();
  return <p>Users refreshed!</p>;
}

Procedure Decoration

Each procedure in the App Router server gets decorated with additional methods.

type DecorateProcedureServer<TType extends ProcedureType, TDef extends ResolverDef> = 
  TType extends 'query'
    ? {
        /** Execute the query procedure */
        query: Resolver<TDef>;
        /** Revalidate cached results for this query */
        revalidate: (input?: TDef['input']) => Promise<{ revalidated: false; error: string } | { revalidated: true }>;
      }
    : TType extends 'mutation'
      ? {
          /** Execute the mutation procedure */
          mutate: Resolver<TDef>;
        }
      : TType extends 'subscription'
        ? {
            /** Execute the subscription procedure */
            subscribe: Resolver<TDef>;
          }
        : never;

Cache Revalidation Endpoint

HTTP endpoint handler for programmatic cache revalidation.

/**
 * HTTP endpoint handler for cache tag revalidation
 * @param req - Request object with JSON body containing cacheTag
 * @returns Response indicating revalidation success or failure
 */
async function experimental_revalidateEndpoint(req: Request): Promise<Response>;

Usage Examples:

// In app/api/revalidate/route.ts
import { experimental_revalidateEndpoint } from "@trpc/next/app-dir/server";

export async function POST(req: Request) {
  return experimental_revalidateEndpoint(req);
}

// Usage from client
async function revalidateUserData() {
  const response = await fetch("/api/revalidate", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ cacheTag: "user.list" }),
  });
  
  const result = await response.json();
  console.log("Revalidated:", result.revalidated);
}

Advanced Usage

Server Components with Error Boundaries

import { Suspense } from "react";
import { ErrorBoundary } from "react-error-boundary";

async function ServerComponentWithTRPC() {
  try {
    const data = await trpc.posts.list.query();
    return (
      <div>
        {data.map(post => (
          <article key={post.id}>
            <h2>{post.title}</h2>
            <p>{post.content}</p>
          </article>
        ))}
      </div>
    );
  } catch (error) {
    return <div>Failed to load posts</div>;
  }
}

export default function PostsPage() {
  return (
    <ErrorBoundary fallback={<div>Something went wrong</div>}>
      <Suspense fallback={<div>Loading posts...</div>}>
        <ServerComponentWithTRPC />
      </Suspense>
    </ErrorBoundary>
  );
}

Streaming with Suspense

import { Suspense } from "react";

async function UserProfile({ userId }: { userId: string }) {
  const user = await trpc.user.getProfile.query({ userId });
  return <div>Profile: {user.name}</div>;
}

async function UserPosts({ userId }: { userId: string }) {
  const posts = await trpc.user.getPosts.query({ userId });
  return (
    <div>
      {posts.map(post => (
        <div key={post.id}>{post.title}</div>
      ))}
    </div>
  );
}

export default function UserPage({ params }: { params: { id: string } }) {
  return (
    <div>
      <Suspense fallback={<div>Loading profile...</div>}>
        <UserProfile userId={params.id} />
      </Suspense>
      <Suspense fallback={<div>Loading posts...</div>}>
        <UserPosts userId={params.id} />
      </Suspense>
    </div>
  );
}

Types

// Resolver function type for App Router procedures
interface Resolver<TDef extends ResolverDef> {
  (input: TDef['input']): Promise<TDef['output']>;
}

// Resolver definition for type safety
interface ResolverDef {
  input: any;
  output: any;
  transformer: boolean;
  errorShape: any;
}

// Procedure types supported
type ProcedureType = 'query' | 'mutation' | 'subscription';

// Router record type
type RouterRecord = Record<string, AnyProcedure | RouterRecord>;

// Any procedure type
interface AnyProcedure {
  _def: {
    type: ProcedureType;
  };
}

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