client-setup

v2026.09.25

Create a vanilla tRPC client with createTRPCClient<AppRouter>(), configure link chain with httpBatchLink/httpLink, dynamic headers for auth, transformer on links (not client constructor). Infer types with inferRouterInputs and inferRouterOutputs. AbortController signal support. TRPCClientError typing.

GitHub
Install command
npx skhub add trpc/client-setup
Markdown
SKILL.md

tRPC -- Client Setup

Setup

// server.ts
import { initTRPC } from '@trpc/server';
import { z } from 'zod';

const t = initTRPC.create();

const appRouter = t.router({
  user: t.router({
    byId: t.procedure
      .input(z.object({ id: z.string() }))
      .query(({ input }) => ({ id: input.id, name: 'Bilbo' })),
    create: t.procedure
      .input(z.object({ name: z.string() }))
      .mutation(({ input }) => ({ id: '1', ...input })),
  }),
});

export type AppRouter = typeof appRouter;
// client.ts
import { createTRPCClient, httpBatchLink } from '@trpc/client';
import type { AppRouter } from './server';

const client = createTRPCClient<AppRouter>({
  links: [
    httpBatchLink({
      url: 'http://localhost:3000/trpc',
    }),
  ],
});

const user = await client.user.byId.query({ id: '1' });
const created = await client.user.create.mutate({ name: 'Frodo' });

Core Patterns

Dynamic Auth Headers

import { createTRPCClient, httpBatchLink } from '@trpc/client';
import type { AppRouter } from './server';

let token = '';

export function setToken(newToken: string) {
  token = newToken;
}

export const client = createTRPCClient<AppRouter>({
  links: [
    httpBatchLink({
      url: 'http://localhost:3000/trpc',
      headers() {
        return {
          Authorization: token ? `Bearer ${token}` : '',
        };
      },
    }),
  ],
});

The headers callback is invoked on every HTTP request, so token changes take effect immediately.

Inferring Procedure Input and Output Types

import type { inferRouterInputs, inferRouterOutputs } from '@trpc/server';
import type { AppRouter } from './server';

type RouterInput = inferRouterInputs<AppRouter>;
type RouterOutput = inferRouterOutputs<AppRouter>;

type UserCreateInput = RouterInput['user']['create'];
type UserByIdOutput = RouterOutput['user']['byId'];

Aborting Requests with AbortController

import { createTRPCClient, httpBatchLink } from '@trpc/client';
import type { AppRouter } from './server';

const client = createTRPCClient<AppRouter>({
  links: [httpBatchLink({ url: 'http://localhost:3000/trpc' })],
});

const ac = new AbortController();
const query = client.user.byId.query({ id: '1' }, { signal: ac.signal });
ac.abort();

Typed Error Handling

import { TRPCClientError } from '@trpc/client';
import type { AppRouter } from './server';

function isTRPCClientError(
  cause: unknown,
): cause is TRPCClientError<AppRouter> {
  return cause instanceof TRPCClientError;
}

try {
  await client.user.byId.query({ id: '1' });
} catch (cause) {
  if (isTRPCClientError(cause)) {
    console.log('tRPC error code:', cause.data?.code);
  }
}

Common Mistakes

[CRITICAL] Missing AppRouter type parameter on createTRPCClient

Wrong:

const client = createTRPCClient({ links: [httpBatchLink({ url })] });

Correct:

import type { AppRouter } from './server';

const client = createTRPCClient<AppRouter>({ links: [httpBatchLink({ url })] });

Without the type parameter, all procedure calls return any and type safety is completely lost.

Source: www/docs/client/vanilla/setup.mdx

[CRITICAL] Transformer goes on individual links, not createTRPCClient

In v11, the transformer option is on individual terminating links, not the client constructor:

import superjson from 'superjson';

createTRPCClient<AppRouter>({
  links: [
    httpBatchLink({
      url: 'http://localhost:3000',
      transformer: superjson,
    }),
  ],
});

In v11, the transformer option was moved from the client constructor to individual terminating links. Passing it to createTRPCClient throws a TypeError.

Source: packages/client/src/internals/TRPCUntypedClient.ts

[CRITICAL] Transformer on server but not on client links

Wrong:

// Server: initTRPC.create({ transformer: superjson })
// Client:
httpBatchLink({ url: 'http://localhost:3000' });

Correct:

// Server: initTRPC.create({ transformer: superjson })
// Client:
httpBatchLink({ url: 'http://localhost:3000', transformer: superjson });

If the server uses a transformer, every terminating link on the client must also specify that transformer. Mismatch causes "Unable to transform response" errors.

Source: https://github.com/trpc/trpc/issues/7083

[CRITICAL] Using import instead of import type for AppRouter

Wrong:

import { AppRouter } from '../server/router';

Correct:

import type { AppRouter } from '../server/router';

A non-type import pulls the entire server bundle into the client. Use import type so it is erased at build time.

Source: www/docs/client/vanilla/setup.mdx

[CRITICAL] Importing appRouter value to derive type in client

Wrong:

import { appRouter } from '../server/router';

type AppRouter = typeof appRouter;

Correct:

// In server: export type AppRouter = typeof appRouter;
// In client:
import type { AppRouter } from '../server/router';

Importing the appRouter value (not just the type) bundles the entire server into the client, shipping server code to the browser.

Source: www/docs/server/routers.md

[CRITICAL] Using type assertions to bypass AppRouter import errors

Wrong:

const client = createTRPCClient<any>({ links: [httpBatchLink({ url })] });

Correct:

// Fix the import path or monorepo configuration
import type { AppRouter } from '@myorg/api-types';

const client = createTRPCClient<AppRouter>({ links: [httpBatchLink({ url })] });

Casting to any or manually recreating the router type destroys end-to-end type safety. Fix the import path or monorepo config instead.

Source: www/docs/client/vanilla/setup.mdx

[CRITICAL] Using createTRPCProxyClient (renamed in v11)

Wrong:

import { createTRPCProxyClient } from '@trpc/client';

Correct:

import { createTRPCClient } from '@trpc/client';

createTRPCProxyClient was renamed to createTRPCClient in v11.

Source: www/docs/client/vanilla/setup.mdx

[CRITICAL] Treating tRPC as a REST API

Wrong:

fetch('/api/trpc/users/123', { method: 'GET' });

Correct:

const user = await client.user.byId.query({ id: '123' });
// Raw equivalent: GET /api/trpc/user.byId?input={"id":"123"}

tRPC uses JSON-RPC over HTTP. Procedures are called by dot-separated name with JSON input, not by REST resource paths.

Source: www/docs/client/overview.md

[HIGH] HTML error page instead of JSON response

If you see couldn't parse JSON, invalid character '<', the tRPC endpoint returned an HTML page (404/503) instead of JSON. This means the url in your link config is wrong or infrastructure routing is misconfigured -- it is not a tRPC bug. Verify the URL matches your adapter's mount point.

Source: www/docs/client/vanilla/setup.mdx

See Also

  • links -- configure httpBatchLink, httpLink, splitLink, and other link types
  • superjson -- set up SuperJSON transformer on server and client
  • server-setup -- define routers, procedures, context, and export AppRouter type
  • react-query-setup -- use tRPC with TanStack React Query for React applications
Discovery
Tags

No tags published for this skill.

Version
Latest version metadata

Version

v2026.09.25

Published

Sep 25, 2026

Category

Uncategorized

License

MIT

Source path

packages/client/skills/client-setup

Default branch

main

Latest commit

8b649ad

Tree SHA

6d43a32