- AI SDK Core unifies model calls, and AI SDK UI gives hooks for chat interfaces.
- A chat consists of a server route with streamText and a component with useChat.
- Keep keys on the server, set limits and add error and status handling.
If you write TypeScript and want to add a chat with a model to an application, the shortest way is through a ready toolkit. The AI SDK describes itself as a TypeScript toolkit for building AI applications and agents with React, Next.js, Vue, Svelte, Node.js and others. It standardizes integrating models of different providers, so you do not have to write separate code for each. The documentation names the main parts: AI SDK Core, a unified API for generating text, structured objects, tool calls and building agents, and AI SDK UI, framework-agnostic hooks for chat interfaces.
Two elements of a minimal chat
A chat consists of two parts. On the server side there is a route that accepts messages, calls the model and returns a stream. On the client side there is a component that displays messages and sends new ones. The examples below come from the chatbot documentation in AI SDK UI as of the checking date; this library’s API changes between versions, so compare it with the current documentation before use.
The server route uses the streamText function, which starts generation and returns the result as a stream:
import { convertToModelMessages, createUIMessageStreamResponse, streamText, toUIMessageStream, UIMessage } from 'ai';
export const maxDuration = 30;
export async function POST(req: Request) {
const { messages }: { messages: UIMessage[] } = await req.json();
const result = streamText({
model: "anthropic/claude-sonnet-5.5",
instructions: 'You are a helpful assistant.',
messages: await convertToModelMessages(messages),
});
return createUIMessageStreamResponse({
stream: toUIMessageStream({ stream: result.stream }),
});
}
The component uses the useChat hook, which according to the documentation manages message streaming, input state, status and errors:
'use client';
import { useChat } from '@ai-sdk/react';
import { DefaultChatTransport } from 'ai';
import { useState } from 'react';
export default function Page() {
const { messages, sendMessage, status } = useChat({
transport: new DefaultChatTransport({ api: '/api/chat' }),
});
const [input, setInput] = useState('');
// render messages (message.parts) and a form calling sendMessage({ text: input })
}
The documentation recommends rendering messages through the parts property, which supports different part types: text, a tool invocation and its result. That way you can later extend the same component with tools.
What happens along the way
- 01TypingThe user sends a message in the form.
- 02RequestuseChat calls the /api/chat route.
- 03ModelstreamText starts generating.
- 04StreamText comes back in pieces.
- 05InterfaceThe component appends message parts.
- 06Statussubmitted, streaming, ready or error.
It is worth understanding the statuses, because the behaviour of the interface depends on them. The documentation lists, among others, a state of a message being submitted and a ready state, and in the example the input field is disabled until the status is ready. This is a simple way to avoid sending a second message in the middle of a response.
What to add next
Once the basic chat works, the next steps are predictable. Save conversations in a database so the user can return to them, and decide how long you keep them. Add a system instruction describing the assistant’s role and the limits of its answers. Limit the length of history sent to the model, because sending the whole conversation each time raises cost and latency. Only then add tools, search over documents or structured data generation. Each of these elements is worth adding one at a time and checking on a set of sample conversations, so you know which one changed the behaviour.
Pitfalls and good practice
| Risk | What to do | |
|---|---|---|
| API key | A leak if it lands in client code | Keep it in server environment variables |
| Cost | Endpoint abuse and long conversations | Limits on requests, context length and spending |
| Errors | Interrupted stream, provider limit | Handle the error status and allow a retry |
| Model output | Untrusted data shown in the interface | Escape it and never execute output as code |
| Privacy | Sensitive data in conversations | Tell the user what is sent |
The most important rule is the API key on the server side only: every visitor sees code in the browser. The second is limits, because a public endpoint with a model and no limits is an invitation to abuse and a bill that will surprise you; I write about calculating costs in the text Task cost in tokens. The third is treating model output as untrusted data. Tools and input validation are the topic of the next article, Tools and validation in JavaScript, and you will find all materials in the JavaScript and Vercel AI SDK hub.
Last checked: 20 October 2026. The library’s API changes between versions; the code in this text matches the documentation of that day.

