useCompletion is for autocomplete-style single responses, not multi-turn chat. The client and server must agree on the stream protocol or parsing fails.

Client (app/page.tsx, 'use client'):

import { useCompletion } from '@ai-sdk/react';

export default function Page() {
  const { completion, input, handleInputChange, handleSubmit, isLoading, error } = useCompletion({
    api: '/api/completion',
  });
  return (
    [form onSubmit={handleSubmit}]
      [input value={input} onChange={handleInputChange} /]
      [button type="submit"]Submit[/button]
      [div]{completion}[/div]
    [/form]
  );
}

Server (app/api/completion/route.ts):

import { streamText } from 'ai';

export const maxDuration = 30;

export async function POST(req: Request) {
  const { prompt } = await req.json();
  const result = streamText({ model: 'your-model-id', prompt });
  return result.toUIMessageStreamResponse();
}

Checklist:
1. useCompletion still manages input/handleSubmit for you (unlike useChat v5+). Do not mix the two hooks' patterns.
2. The server returns a UI message stream via result.toUIMessageStreamResponse(). The older toTextStream/createTextStreamResponse pair is for raw text protocols only.
3. If you serve a raw text stream instead, the client must opt into it with new TextStreamChatTransport on useCompletion, or you get Failed to parse stream string errors.
4. completion holds the streamed text; isLoading and error cover the request lifecycle. Render error instead of swallowing it.
5. A classic bug: calling setState during render from completion updates, which throws Maximum update depth exceeded. Derive UI in effects or memo, never setState inline in the render body.