Most teams adopting the Vercel AI SDK hit a fork early: which React hook should own the streaming state? The vercel ai sdk usecompletion vs usechat decision dictates how you model conversation history, how much payload you ship to the server, and which backend route shape you commit to, so it deserves a concrete comparison instead of a copy-paste from docs.
Capabilities
When evaluating vercel ai sdk usecompletion vs usechat, the first axis is state shape. useCompletion is a single-shot text generation hook. You give it a prompt (or call complete(prompt)), it streams a string back. There is no built-in concept of turns, roles, or message history. It is the right primitive for autocomplete, summarization endpoints, or any UI where the user triggers one generation and gets one result.
import { useCompletion } from '@ai-sdk/react';
function SummarizeBox() {
const { completion, complete, isLoading } = useCompletion({
api: '/api/summarize',
});
return (
<div>
<button onClick={() => complete(longText)} disabled={isLoading}>
Summarize
</button>
<p>{completion}</p>
</div>
);
}
useChat manages a messages array with { role, content } objects, handles submit, input binding, and error resets. It is built for multi-turn chat: each call appends a user message, calls the server, and appends the assistant message. It also supports attachments and custom body fields.
import { useChat } from '@ai-sdk/react';
function ChatUI() {
const { messages, input, handleInputChange, handleSubmit } = useChat({
api: '/api/chat',
});
return (
<form onSubmit={handleSubmit}>
<input value={input} onChange={handleInputChange} />
{messages.map((m) => (
<div key={m.id}>{m.role}: {m.content}</div>
))}
</form>
);
}
On the server, useCompletion pairs with streamText and a simple { prompt } body. useChat pairs with streamText but receives { messages }. The hook choice forces a contract.
Streaming internals
Both hooks consume the same underlying readStreamableValue machinery and Server-Sent Events. The difference is purely the shape of the request and the state reducer. useCompletion keeps a single completion string; useChat keeps an array and reconciles streaming tokens into the last assistant message.
Backend Route Shape
The server side is minimal and mirrors the hook. For useCompletion:
// app/api/summarize/route.ts
import { streamText } from 'ai';
import { openai } from '@ai-sdk/openai';
export async function POST(req: Request) {
const { prompt } = await req.json();
const result = streamText({ model: openai('gpt-4o-mini'), prompt });
return result.toDataStreamResponse();
}
For useChat:
// app/api/chat/route.ts
import { streamText } from 'ai';
import { openai } from '@ai-sdk/openai';
export async function POST(req: Request) {
const { messages } = await req.json();
const result = streamText({ model: openai('gpt-4o-mini'), messages });
return result.toDataStreamResponse();
}
The only difference is destructuring prompt versus messages. Both return the same data stream response format, so you can swap providers without touching the client hook.
Price / Cost Model
Neither hook charges you anything—cost lives in the model inference call. The driver is token count. With useCompletion you control exactly what goes into the prompt; typical input is small and deterministic. With useChat the client sends the entire visible messages array every turn, so input tokens grow linearly with conversation depth unless you trim.
If you proxy model calls through a gateway such as n4n.ai, per-token metering is identical regardless of which hook you use; the only variable is how many historical tokens your client elects to send. A useChat route that forwards all messages unmodified will cost more than a useCompletion route that sends a synthesized prompt.
// example request body from useChat to /api/chat
{
"messages": [
{ "role": "user", "content": "hi" },
{ "role": "assistant", "content": "hello" },
{ "role": "user", "content": "explain tokens" }
]
}
// example request body from useCompletion to /api/summarize
{
"prompt": "Summarize: ..."
}
Latency / Throughput
Time-to-first-token (TTFT) is dominated by the model, not the hook. However, useChat serializes a larger JSON body and the server must reconstruct a full conversation context before generation starts. For a 20-turn thread, that parsing is still sub-millisecond on Node, but the upstream model receives more input tokens, which shifts queue time and prefill cost.
Throughput on the client is equivalent: both update React state on each chunk. useChat does a slightly heavier reducer pass to locate the streaming message id. In practice the difference is invisible until you cross hundreds of messages in a single session. On slow mobile networks, the extra kilobytes from useChat history can add noticeable request latency before the first token arrives.
Ergonomics
useCompletion is a three-field hook: completion, complete, isLoading. You can wrap it in any component without ceremony. useChat hands you messages, input, handleInputChange, handleSubmit, reload, stop, and error. That is more power but more opinionated DOM expectations.
If you need to show a rolling transcript, useChat saves you from writing a reducer. If you need a custom UX—like a diff view or a cursor-following ghost text—useCompletion stays out of your way.
// custom ghost text with useCompletion
const { completion } = useCompletion({ api: '/api/ghost' });
return <span className="ghost">{completion}</span>;
useChat also exposes lifecycle callbacks like onFinish and onError that are wired to the message lifecycle. With useCompletion you get onFinish on the single string, which is simpler to reason about.
Ecosystem
Both ship in @ai-sdk/react and share the same ai core. They work with any provider that has an @ai-sdk/ adapter (OpenAI, Anthropic, etc.) and with the streamText / streamUI server functions. useChat has first-class support in the Vercel AI SDK’s Chat components and community libraries like ai-chat-ui. useCompletion has no dedicated component ecosystem; you build the view.
Tool calling and structured generation are available in both via server-side streamText options, but useChat exposes addToolResult and message parts for rendering tool invocations. If your product relies on function calling UI, useChat is the only sane path.
Limits
useCompletion limits:
- No conversation memory; you must manually inject prior context into the prompt.
- No standardized error recovery for multi-step flows.
- Max output is whatever the model caps; the hook does not chunk across requests.
useChat limits:
- Context window overflow: the hook does not auto-summarize or drop messages. You must implement
experimental_prepareRequestBodyor a server-side trim. - Larger payloads on slow networks.
- State can become stale if you mutate
messagesoutside the hook’s API.
Head-to-Head Table
| Dimension | useCompletion | useChat |
|---|---|---|
| Capabilities | Single-shot streaming text | Multi-turn message array, roles, tools |
| Cost driver | Prompt size you define | Full history sent each turn |
| Latency | Minimal body, low prefill | Larger body, grows with turns |
| Ergonomics | 3-state, unopinionated | Full chat state machine |
| Ecosystem | Bare hook, custom UI | Chat components, tool UI |
| Limits | No memory, manual context | Context overflow, payload size |
Which to Choose
Single-generation surfaces. Autocomplete, summarization buttons, SQL generators, or any UI where the user provides one input and expects one output: use useCompletion. You avoid shipping stale history and keep the request contract tiny.
Conversational products. Customer support chat, coding assistants with back-and-forth, or any thread that displays prior turns: use useChat. The built-in message state and submit handling will save you a week of reducer bugs.
Hybrid interfaces. Some apps show a chat panel but also a “regenerate title” field. Use useChat for the panel and a separate useCompletion instance for side actions. They can share the same backend provider; only the route body differs.
High-volume or cost-sensitive. If token spend is critical, useCompletion with server-side context compression beats useChat left untrimmed. But you can also keep useChat and trim messages in experimental_prepareRequestBody before they hit the model.
Tool-augmented UX. Anything rendering function calls or structured parts must use useChat—the message parts model is required for stable tool result handling.
The vercel ai sdk usecompletion vs usechat split is not about which is newer or better; it is about whether your UI owns a conversation or a cursor. Pick the hook that matches the state shape you already have, and the backend route follows naturally.