Skip to content
AI Engineering
AI Engineering11 min read0 views

Build a Streaming AI Chat Agent with tRPC v11 + Next.js 15 (2026)

Use tRPC v11 async generators to stream OpenAI tokens over SSE through a fully type-safe stack. Real Next.js App Router code, no WebSockets needed.

TL;DR — tRPC v11 added async function* procedures that stream over Server-Sent Events. Wrap an OpenAI stream: true call and you get end-to-end type-safe token streaming with zero WebSocket plumbing.

What you'll build

A Next.js 15 App Router chat UI with a tRPC chat.stream mutation that yields OpenAI tokens one at a time. The client uses the new useSubscription hook so React renders deltas as they arrive — no manual fetch + reader code.

Prerequisites

  1. Next.js 15.x + App Router, TypeScript 5.5+.
  2. @trpc/server@^11, @trpc/client@^11, @trpc/react-query@^11, zod@^3.23.
  3. openai@^4.70 and an API key.

Architecture

flowchart LR
  UI[React useSubscription] -- SSE --> RT[tRPC HTTP route]
  RT -- async generator --> OA[OpenAI stream: true]
  OA -- delta --> RT --> UI

Step 1 — tRPC router with async generator

```ts import { z } from "zod"; import OpenAI from "openai"; import { router, publicProcedure } from "./trpc";

const oa = new OpenAI();

export const chatRouter = router({ stream: publicProcedure .input(z.object({ messages: z.array(z.object({ role: z.enum(["user","assistant","system"]), content: z.string(), })) })) .subscription(async function* ({ input, signal }) { const s = await oa.chat.completions.create({ model: "gpt-4o-mini", messages: input.messages, stream: true, }, { signal }); for await (const chunk of s) { const delta = chunk.choices[0]?.delta?.content; if (delta) yield { type: "delta", text: delta }; } yield { type: "done" }; }), }); ```

Step 2 — Mount the route handler

```ts // app/api/trpc/[trpc]/route.ts import { fetchRequestHandler } from "@trpc/server/adapters/fetch"; import { appRouter } from "@/server/router";

Hear it before you finish reading

Talk to a live CallSphere AI voice agent in your browser — 60 seconds, no signup.

Try Live Demo →

const handler = (req: Request) => fetchRequestHandler({ endpoint: "/api/trpc", req, router: appRouter, createContext: () => ({}) });

export { handler as GET, handler as POST }; ```

```ts import { createTRPCReact, httpSubscriptionLink, splitLink, httpBatchLink } from "@trpc/react-query";

export const trpc = createTRPCReact(); export const trpcClient = trpc.createClient({ links: [splitLink({ condition: (op) => op.type === "subscription", true: httpSubscriptionLink({ url: "/api/trpc" }), false: httpBatchLink({ url: "/api/trpc" }), })], }); ```

Step 4 — useSubscription in a component

```tsx "use client"; import { useState } from "react"; import { trpc } from "@/lib/trpc";

export function Chat() { const [text, setText] = useState(""); const [reply, setReply] = useState(""); const [run, setRun] = useState<any[]>([]);

trpc.chat.stream.useSubscription( { messages: run }, { enabled: run.length > 0, onData: (e) => e.type === "delta" && setReply((r) => r + e.text) }, );

Still reading? Stop comparing — try CallSphere live.

CallSphere ships complete AI voice agents per industry — 14 tools for healthcare, 10 agents for real estate, 4 specialists for salons. See how it actually handles a call before you book a demo.

return ( <form onSubmit={(e) => { e.preventDefault(); setReply(""); setRun([{ role: "user", content: text }]); }}> <input value={text} onChange={(e) => setText(e.target.value)} />

{reply}
); } ```

Step 5 — Cancel mid-stream

When the user navigates away, React Query unmounts the subscription. tRPC v11 propagates the abort signal into the OpenAI client ({ signal }), which cancels the upstream request — saving tokens.

Pitfalls

  • Edge runtime: runtime = "edge" works for SSE but limits some Node-only OpenAI helpers — pick runtime = "nodejs" if you need stream.controller.
  • Buffering proxies: NGINX, Cloudflare, Vercel default proxies can buffer SSE — set X-Accel-Buffering: no or use responseInit: { headers: { "X-Accel-Buffering": "no" } }.
  • JSON-line vs SSE: tRPC v11 auto-picks SSE for browsers; if you call from a non-browser client, switch to httpLink and parse JSON-lines.

How CallSphere does this in production

CallSphere's Sales product (Node.js 20 + React 18 + Vite) ships a near-identical tRPC pipe for real-time SDR chat across 37 agents, 90+ tools, and 115+ DB tables — used in 6 verticals at $149/$499/$1,499 pricing with a 14-day no-card trial and 22% affiliate. p95 first-token latency is ~280ms.

FAQ

Does this work with React Server Components? The streaming part is a client component, but the trigger can be a server action that primes run.

Why not WebSockets? SSE is HTTP/1.1 friendly, survives serverless, and tRPC v11 picked it as the default for that reason.

Can I stream tool calls? Yes — yield { type: "tool_call", name, args } from the generator and render a tool UI on the client.

What about Vercel AI SDK? AI SDK and tRPC are complementary — wrap streamText inside a tRPC subscription if you want both.

Sources

Share

Try CallSphere AI Voice Agents

See how AI voice agents work for your industry. Live demo available -- no signup required.