Integrate with Next.js and React
Keep the provider and Runner on the server, then stream normalized UI events to a browser-safe chat client.
The browser package never runs providers or tools. A React client sends user input to your route; the route owns identity, credentials, policy, tool execution, and durable sessions.
Install
bun add @zhivex-ai/react @zhivex-ai/sdk @zhivex-ai/openai react react-dom
Create the server route
In app/api/chat/route.ts:
import {
Agent,
createPostgresSessionService,
createRunner,
fromUIMessage,
toUIRunnerStreamResponse,
type AgentApprovalResponse,
type UIMessage
} from "@zhivex-ai/sdk";
import { createOpenAI } from "@zhivex-ai/openai";
export const runtime = "nodejs";
const openai = createOpenAI({ apiKey: process.env.OPENAI_API_KEY });
const runner = createRunner({
appName: "support-chat",
agent: new Agent({
model: openai("gpt-4o-mini"),
instructions: "Answer clearly and briefly."
}),
sessionService: createPostgresSessionService({ client: postgresClient })
});
export async function POST(request: Request) {
const body = await request.json() as {
message?: UIMessage;
sessionId?: string;
approvals?: AgentApprovalResponse[];
};
const userId = await resolveCurrentUserId(request);
if (!body.message && !body.approvals?.length) {
return Response.json({ error: "Missing message or approval." }, { status: 400 });
}
const stream = runner.stream({
userId,
sessionId: body.sessionId,
messages: body.message ? [fromUIMessage(body.message)] : undefined,
approvals: body.approvals,
abortSignal: request.signal
});
return toUIRunnerStreamResponse(stream);
}
postgresClient and resolveCurrentUserId() belong to the application. Map the authenticated identity to the SDK call; never trust a browser-supplied user or tenant id as authorization.
Add the React client
Import the default stylesheet once from app/layout.tsx:
import "@zhivex-ai/react/styles.css";
Then create a client component:
"use client";
import { ZhivexChat, useZhivexChat } from "@zhivex-ai/react";
export function SupportChat() {
const chat = useZhivexChat({ endpoint: "/api/chat" });
return (
<ZhivexChat
controller={chat}
header={<strong>Support assistant</strong>}
starterPrompts={[
"Summarize the latest updates",
"Help me plan a rollout"
]}
/>
);
}
The default transport sends the latest user message, current session id, and approval decisions. Runner + SessionService remains the source of truth for durable history.
Configure the React 0.3 experience
The ready-made chat now consumes the controller’s run activity, multimodal send API, status, copy, and optional retry capabilities. It also supports explicit themes and compact density without replacing the default stylesheet:
<ZhivexChat
controller={chat}
density="compact"
theme="dark"
formatError={() => "The assistant could not complete this request."}
composerProps={{
accept: "image/*,application/pdf",
maxAttachments: 3,
maxAttachmentBytes: 2 * 1024 * 1024
}}
messageListProps={{
approvalCardProps: {
reasonRequired: true,
description: "This action sends data outside the current workspace."
}
}}
/>
- Attachments can be selected, pasted, or dropped; keep count and byte limits narrow or upload larger files through an application-owned flow.
- Transport and server details are hidden from users by default. Return only reviewed text from
formatError; reserveshowErrorDetailsfor trusted diagnostics. themeacceptssystem,light, ordark; every primitive exposes a stabledata-slotfor application-owned styling.- Retry remains opt-in because replay can duplicate durable history. Enable it only when the endpoint implements idempotent regeneration.
React 0.3 changes the ready-made visual system. Recheck screenshots, CSS-variable overrides, focus behavior, compact layouts, and mobile attachment flows before rollout.
Production checklist
- authenticate and rate-limit before provider work;
- use Postgres instead of local files on serverless infrastructure;
- validate request sizes and tool inputs;
- pass
request.signalinto the runner and downstream I/O; - redact sensitive payloads before trace export;
- apply a narrow allowlist before rendering remote media.
See observability and evaluations and the production guide before exposing the route to real users.