# Streaming UI with the AI SDK: route handler, useChat and parts

Streaming with the AI SDK is a route handler returning parts and a component that reads them: text appears as it arrives, a tool call as its own part.

> Page: https://ui.eneawork.it/blog/ai-sdk-streaming-ui
> Markdown: https://ui.eneawork.it/blog/ai-sdk-streaming-ui.md

*2026-11-01*

Lo streaming con l'AI SDK è un route handler che restituisce uno stream di parti e un componente che legge message.parts: il testo compare mentre arriva, e una chiamata a un tool compare come parte separata. Non serve gestire a mano il protocollo.

Il pacchetto ai sul registry npm, letto il 1 novembre 2026, è alla versione 7.0.159: 714 file, 8.467.517 byte scompattati, 3 dipendenze dirette, zod come peer dependency e Node 22 come minimo dichiarato. La guida Next.js App Router su ai-sdk.dev, letta lo stesso giorno, espone streamText, convertToModelMessages, createUIMessageStreamResponse e toUIMessageStream, con useChat che restituisce messages e sendMessage. Nella stessa guida stopWhen vale isStepCount(1) per default, quindi una tool call chiude la generazione se non lo alzi. L'endpoint EneUI ha risposto a tools/list con 3.655 byte in 218 ms, misurati il 1 novembre 2026. Il catalogo dietro conta 111.985 componenti, 1.556 blocchi e 12.808 icone da 1.472 librerie, letti da /api/stats.

## Tre file, un percorso

import { streamText, convertToModelMessages, createUIMessageStreamResponse, toUIMessageStream, type UIMessage, } from 'ai'; export async function POST(req: Request) { const { messages }: { messages: UIMessage[] } = await req.json(); const result = streamText({ model: 'anthropic/claude-sonnet-5.5', messages: await convertToModelMessages(messages), }); return createUIMessageStreamResponse({ stream: toUIMessageStream({ stream: result.stream }), }); }

Il route handler non aspetta la risposta completa: restituisce un oggetto stream e il framework lo invia al browser mentre il modello scrive. convertToModelMessages toglie dai messaggi della UI i campi che il modello non deve vedere, come identificativi e tempi.

## La chiave resta sul server

Il modello si sceglie nel route handler, non nel componente. Una chiave in una variabile NEXT_PUBLIC_ finisce nel bundle e chiunque la legge dal browser. Se il modello deve cambiare per utente o per piano, la decisione si prende sul server dopo aver letto la sessione: il client manda i messaggi, non il nome del modello.

## Le parti sono l'interfaccia

'use client'; import { useChat } from '@ai-sdk/react'; import { useState } from 'react'; export default function Chat() { const [input, setInput] = useState(''); const { messages, sendMessage } = useChat(); return ( {messages.map((m) => m.parts.map((part, i) => { if (part.type === 'text') return {part.text}; if (part.type === 'tool-search_components') return {JSON.stringify(part, null, 2)}; return null; }), )} { e.preventDefault(); sendMessage({ text: input }); setInput(''); }} > setInput(e.target.value)} /> ); }

message.parts è un array ordinato: contiene testo, ragionamento e tool call nello stesso ordine in cui il modello li ha prodotti. Una tool call si chiama tool- più il nome che hai dato al tool, quindi tool-search_components. Non c'è HTML da interpretare e non c'è un formato da indovinare.

## Multi-step: alzare stopWhen

Con il default isStepCount(1) la generazione si ferma alla prima tool call: il risultato viene eseguito, ma il modello non lo usa per rispondere. Per un agente che cerca, legge e poi scrive serve stopWhen: isStepCount(5), con un tetto esplicito. Il numero è una scelta di prodotto: ogni passo in più costa un prompt intero.

## Cosa non fa lo streaming

## La limitazione

Il pacchetto ai 7.0.159 occupa 8,4 MB scompattati in 714 file, richiede Node 22 e zod come peer dependency: è una dipendenza che pesa, non un hook da due righe. La major 7 ha cambiato l'API rispetto alle versioni precedenti, quindi un tutorial vecchio produce errori di tipo invece di una UI: blocca la versione nel lockfile e leggi la guida della major che usi.

## Dove continuare

Lo streaming lato server senza SDK è in streaming e Suspense in Next.js, la differenza fra route handler e server action in route handler o server action, il rendering sospeso in Suspense in React. Le parti pronte per una chat sono in 111.985 componenti e 1.556 blocchi; un esempio con licenza dichiarata a monte è chatgpt prompt input di Jahed.

## FAQ

### Devo usare Next.js per lo streaming con l'AI SDK?

No. Serve un endpoint che restituisca uno stream e un client che lo legga; la guida ufficiale usa Next.js App Router perché è il percorso più breve. Su Vite, Express o un altro framework cambi il trasporto, non il modello di dati.

### Dove metto la chiave del provider?

In una variabile d'ambiente letta solo dal server, come AI_GATEWAY_API_KEY in .env.local. Una chiave con prefisso pubblico finisce nel bundle e chiunque la può leggere dal browser.

### Come mostro una tool call invece del testo?

Leggendo message.parts e trattando la parte il cui type è tool- più il nome del tool, per esempio tool-search_components. La parte porta input e risultato, quindi puoi mostrarla come JSON senza interpretare stringhe.

### Perché il modello non risponde dopo la tool call?

Perché stopWhen vale isStepCount(1) per default: la generazione finisce alla prima chiamata. Alza il limite, per esempio a 5, se vuoi che il modello usi il risultato del tool per chiudere la risposta.

### Lo streaming riduce i token o il costo?

No. I token sono gli stessi, cambia solo quando l'utente li vede. Il costo si riduce tagliando contesto e passi, non cambiando il modo in cui il testo arriva al browser.
