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.