The App Router chat endpoint has one correct shape in AI SDK 7. Get any piece wrong and the client either hangs, never aborts, or throws a parse error.
app/api/chat/route.ts:
import { convertToModelMessages, createUIMessageStreamResponse, streamText, toUIMessageStream } from 'ai';
export const maxDuration = 30;
export async function POST(req: Request) {
const { messages } = await req.json();
const result = streamText({
model: 'your-model-id',
instructions: 'You are a helpful assistant.',
messages: await convertToModelMessages(messages),
abortSignal: req.signal,
});
return createUIMessageStreamResponse({
stream: toUIMessageStream({ stream: result.stream }),
});
}
Checklist:
1. messages from useChat are UIMessage objects, not model messages. Convert them with convertToModelMessages, and await it (it is async since v6).
2. Pass abortSignal: req.signal so a client stop() actually cancels the provider request instead of leaving a zombie generation running.
3. In AI SDK 7 the prompt option for system-level text is instructions, not system. The v7 codemod is npx @ai-sdk/codemod v7/rename-system-to-instructions.
4. Return createUIMessageStreamResponse, not a raw Response. The UI message stream protocol is what useChat's DefaultChatTransport parses.
5. Set export const maxDuration = 30 on the route. Vercel serverless functions time out otherwise, and long generations die mid-stream.
6. Never call streamText in a client component or inside the page that renders useChat. Route handlers are server-only; the 'use client' component talks to them over HTTP.