Documentation
Documentation
Introduction

Getting Started

Getting StartedInstallationQuick StartProject Structure

Architecture

Architecture OverviewTech StacktRPC MiddlewareDesign Principles

Patterns

Code Patterns & ConventionsFeature ModulesError HandlingType Safety

Database

DatabaseSchema DefinitionDatabase OperationsMigrationsCaching

API

tRPCProceduresRouterstRPC Proxy Setup
APIsOpenAPIREST Endpoints

Auth & Access

AuthenticationConfigurationOAuth ProvidersRolesSession Management
AuthorizationUser RolesPermissions

Routing & i18n

RoutingDeclarative RoutingNavigation
InternationalizationTranslationsLocale Routing

Components & UI

ComponentsButtonsFormsNavigationDialogs
StylesTailwind CSSThemingTypography

Storage

StorageConfigurationUsageBuckets

Configuration

ConfigurationEnvironment VariablesFeature Flags

Templates

Template GuidesCreate New FeatureCreate New PageCreate Database TableCreate tRPC RouterAdd Translations

Development

DevelopmentCommandsAI AgentsBest Practices

tRPC Proxy Setup

tRPC proxy configuration for server and client integration

Overview

The tRPC proxy setup provides type-safe communication between client and server components. It consists of two main parts:

  1. Server-side caller (src/trpc/server.ts) - For Server Components and API routes
  2. Client-side React provider (src/trpc/react.tsx) - For Client Components with React Query

Both leverage the tRPC router defined in src/trpc/procedures/root.ts to ensure end-to-end type safety.

Server-Side Proxy

The server proxy enables calling tRPC procedures from React Server Components without HTTP overhead.

Configuration (src/trpc/server.ts)

import "server-only";
import { createCaller } from "@/trpc/procedures/root";
import { createTRPCContext } from "@/trpc/procedures/trpc";
import { createHydrationHelpers } from "@trpc/react-query/rsc";
import { cache } from "react";

// Create context with headers and cookies
const createContext = cache(async () => {
  const h = new Headers(await headers());
  const c = await cookies();
  
  return createTRPCContext({
    headers: h,
    getCookie: (name: string) => c.get(name)?.value,
    setCookie: (name, value, options) => {
      // Note: Cookie setting not supported in RSC context
      // Use client-side mutations for auth flows
    },
  });
});

// Create server caller
const caller = createCaller(createContext);

// Export hydration helpers
export const { trpc: api, HydrateClient } = createHydrationHelpers(
  caller,
  getQueryClient
);

Usage in Server Components

import { api } from "@/trpc/server";

export default async function UsersPage() {
  // Direct server-side call (no HTTP request)
  const users = await api.users.list();
  
  return (
    <div>
      {users.payload?.map(user => (
        <div key={user.id}>{user.name}</div>
      ))}
    </div>
  );
}

Prefetching for Client Components

import { api, HydrateClient } from "@/trpc/server";

export default async function Page() {
  // Prefetch data in Server Component
  await api.users.list.prefetch();
  
  return (
    <HydrateClient>
      {/* Client component will have instant data */}
      <UserList />
    </HydrateClient>
  );
}

The server-side caller cannot set cookies due to RSC limitations. Use client-side mutations for authentication flows that require cookie setting.

Client-Side Proxy

The client proxy integrates tRPC with React Query for reactive data fetching in Client Components.

Configuration (src/trpc/react.tsx)

import { createTRPCReact } from "@trpc/react-query";
import { httpBatchLink } from "@trpc/client";
import SuperJSON from "superjson";

export const api = createTRPCReact<AppRouter>();

export function TRPCReactProvider({ children, headers }) {
  const [trpcClient] = useState(() =>
    api.createClient({
      links: [
        httpBatchLink({
          transformer: SuperJSON,
          url: absoluteUrl(ApiTrpc()),
          headers() {
            return {
              "x-trpc-source": "nextjs-react",
            };
          },
          fetch(url, options) {
            return fetch(url, {
              ...options,
              credentials: "include", // Enable cookies
            });
          },
        }),
      ],
    })
  );
  
  return (
    <QueryClientProvider client={queryClient}>
      <api.Provider client={trpcClient} queryClient={queryClient}>
        {children}
      </api.Provider>
    </QueryClientProvider>
  );
}

Usage in Client Components

"use client";

import { api } from "@/trpc/react";

export function UserList() {
  // React Query hook with type safety
  const { data, isLoading } = api.users.list.useQuery();
  
  if (isLoading) return <div>Loading...</div>;
  
  return (
    <div>
      {data?.payload?.map(user => (
        <div key={user.id}>{user.name}</div>
      ))}
    </div>
  );
}

Mutations

"use client";

import { api } from "@/trpc/react";

export function CreateUserForm() {
  const utils = api.useUtils();
  
  const createUser = api.users.create.useMutation({
    onSuccess: () => {
      // Invalidate queries to refetch data
      utils.users.list.invalidate();
    },
  });
  
  return (
    <button onClick={() => createUser.mutate({ name: "John" })}>
      Create User
    </button>
  );
}

React Query Integration

The proxy setup includes React Query for powerful data management:

  • Automatic caching - Queries are cached by default
  • Background refetching - Stale data is refetched automatically
  • Optimistic updates - Update UI before server confirms
  • Infinite queries - Built-in pagination support
  • Cache invalidation - Fine-grained control over refetching

Cache Invalidation Patterns

const utils = api.useUtils();

// Invalidate specific query
utils.users.list.invalidate();

// Invalidate with parameters
utils.users.getById.invalidate({ id: "123" });

// Invalidate all users queries
utils.users.invalidate();

// Refetch immediately
await utils.users.list.refetch();

Optimistic Updates

const updateUser = api.users.update.useMutation({
  onMutate: async (newData) => {
    // Cancel ongoing queries
    await utils.users.getById.cancel({ id: newData.id });
    
    // Get current data
    const previous = utils.users.getById.getData({ id: newData.id });
    
    // Optimistically update
    utils.users.getById.setData({ id: newData.id }, newData);
    
    return { previous };
  },
  onError: (err, newData, context) => {
    // Rollback on error
    utils.users.getById.setData(
      { id: newData.id }, 
      context?.previous
    );
  },
});

Middleware Chain

The tRPC proxy respects the middleware chain defined in the router. Each request goes through:

  1. Context creation - Auth, database, headers
  2. Rate limiting - API key or user-based limits
  3. Error handling - Consistent error formatting
  4. Request logging - Development debugging

For detailed middleware information, see Middleware Architecture.

Type Safety

The proxy maintains full type safety across the stack:

// Server procedure definition
export const userRouter = createTRPCRouter({
  getById: publicProcedure
    .input(z.object({ id: z.string().uuid() }))
    .query(async ({ input }) => {
      const user = await db.users.findById(input.id);
      return { success: true, payload: user };
    }),
});

// Client usage - fully typed!
const { data } = api.users.getById.useQuery({ id: "123" });
//     ^? { success: boolean; payload: User | undefined }

// TypeScript error if ID is not UUID
api.users.getById.useQuery({ id: "invalid" }); // ❌ Error

Performance Optimization

Batch Requests

The httpBatchLink automatically batches multiple requests:

// These are batched into a single HTTP request
const user = api.users.getById.useQuery({ id: "1" });
const posts = api.posts.list.useQuery();
const comments = api.comments.list.useQuery();

Request Deduplication

React Query deduplicates identical requests:

// Only makes one request, even if called multiple times
function Component1() {
  api.users.list.useQuery();
}

function Component2() {
  api.users.list.useQuery(); // Uses same request
}

Error Handling

The proxy automatically handles errors and provides consistent formatting:

const mutation = api.users.create.useMutation({
  onError: (error) => {
    // error is typed based on your error handling
    if (error.data?.code === "UNAUTHORIZED") {
      redirect("/login");
    }
    
    toast.error(error.message);
  },
});

Next Steps

  • tRPC Routers - Creating tRPC routers
  • tRPC Procedures - Defining procedures
  • Middleware - Middleware chain details
  • Error Handling - Error patterns

On this page

Overview
Server-Side Proxy
Configuration (src/trpc/server.ts)
Usage in Server Components
Prefetching for Client Components
Client-Side Proxy
Configuration (src/trpc/react.tsx)
Usage in Client Components
Mutations
React Query Integration
Cache Invalidation Patterns
Optimistic Updates
Middleware Chain
Type Safety
Performance Optimization
Batch Requests
Request Deduplication
Error Handling
Next Steps