Desarrollo IANext.js

Chatbot IA con Next.js 2026: El Tutorial de Streaming

23 de agosto de 2026
11 min de lectura
Chatbot IA con streaming construido con Next.js y Vercel AI SDK
Compartir:

Toda demo de chatbot parece instantánea hasta que la publicás. La diferencia entre un juguete que imprime la respuesta completa después de ocho segundos de silencio y un producto que los usuarios aman es una sola técnica: stremear tokens a la pantalla a medida que el modelo los genera.

En este deep-dive te muestro cómo construir un chatbot IA con streaming usando Next.js y Vercel AI SDK (v7, vigente en 2026): el route handler, el hook useChat, el modelo de partes de mensaje y el checklist de producción que uso.

1. El Problema: Las Respuestas Bloqueantes Se Sienten Rotas

Un chatbot ingenuo espera el completion completo y recién ahí lo renderiza. Con modelos reales eso significa de 3 a 10 segundos de UI muerta: sin feedback, sin progreso, sin confianza. Los usuarios reintentan, envían doble o se van — y cada reintento te factura otra generación completa.

El Error Más Común

Tratar el endpoint de chat como un endpoint REST que devuelve JSON. El chat es un stream, no un par request/response. Si tu /api/chat acumula toda la respuesta antes de responder, reconstruiste lo peor de las demos de 2023.

El streaming arregla la percepción y la ingeniería: primer token en unos 300ms, tokens renderizándose en vivo, un solo round trip HTTP sobre un ReadableStream. Vercel AI SDK se encarga de esa plomería — helpers de servidor para producir el stream y el hook useChat para consumirlo.

2. Conceptos Mínimos (Cinco Minutos)

Solo necesitás seis ideas antes de tocar código. Todo lo demás en el SDK es una variación de estas, así que aprendelas una vez y el tutorial se lee solo.

💬

UIMessage

ai · tipo del cliente

La forma de mensaje que tu UI posee: id, rol y un array parts con texto, tool calls y reasoning en orden de generación.

🔄

streamText

ai · núcleo del servidor

Ejecuta el modelo con streaming de tokens. Devuelve un resultado cuyo stream enviás al cliente — nunca esperes el texto completo.

🧹

convertToModelMessages

ai · adaptador

Quita metadata de UI como timestamps e info del remitente, convirtiendo UIMessage[] en el ModelMessage[] que el modelo espera.

🌊

createUIMessageStreamResponse

ai · transporte

Serializa el stream del modelo al protocolo de UI-message con toUIMessageStream y lo devuelve como respuesta HTTP con streaming.

🪝

useChat

@ai-sdk/react · hook

Estado del cliente para todo el chat: messages, sendMessage, status, error. Apunta a POST /api/chat por defecto — sin código fetch.

🧩

message.parts

renderizá esto, no .content

Cada mensaje se renderiza desde su array parts: hacé switch sobre part.type (texto, tools, reasoning) y renderizá en orden.

El Modelo Mental

El cliente posee UIMessage[] y lo POSTea a /api/chat. El servidor convierte a mensajes del modelo, streamText genera tokens, el stream de UI-message vuelve en un solo round trip HTTP y useChat agrega las partes en vivo. Primer token en unos 300ms.

3. El Tutorial, Paso a Paso

De un proyecto Next.js App Router vacío a un chat con streaming en seis pasos. Verifiqué cada import contra la documentación oficial de AI SDK v7 — useChat viene de @ai-sdk/react, nunca del eliminado ai/react.

🛠️ Pasos de Construcción

Paso 1 · Scaffold

Ejecutá pnpm create next-app@latest my-ai-app, aceptá App Router y Tailwind, luego cd my-ai-app. Node 22 o superior es obligatorio — el campo engines del paquete ai lo exige.

Paso 2 · Instalación

Ejecutá pnpm add ai @ai-sdk/react zod. El provider de Gateway viene dentro de ai; agregá @ai-sdk/openai solo si llamás a OpenAI directo.

Paso 3 · API key

Creá .env.local con AI_GATEWAY_API_KEY=xxxxxxxxx para acceder a cientos de modelos con una sola key. Nunca subas este archivo a git.

Paso 4 · Route handler

Creá app/api/chat/route.ts: parseá UIMessage[], llamá streamText, devolvé createUIMessageStreamResponse. Código completo en la sección 4.

Paso 5 · UI del chat

Reemplazá app/page.tsx con un client component que use useChat y sendMessage, renderizando message.parts. Código completo en la sección 4.

Paso 6 · Ejecución

Ejecutá pnpm run dev, abrí http://localhost:3000, enviá un mensaje. Los tokens se renderizan en vivo — ese loop de streaming es toda la base.

🛡️ Endurecé Antes de Publicar

maxDuration = 30

Exportá maxDuration desde el archivo de ruta para que la función serverless viva lo suficiente para terminar el stream.

Rate limit en /api/chat

Diez requests por minuto por IP con sliding window de Upstash. Una URL de chat abierta sin límites es la API gratis de otro.

Exigí autenticación

Verificá la sesión antes de llamar streamText. Cualquier cliente que descubra la URL puede gastar tu presupuesto del modelo.

📚 Machete de la API

streamText

Generación del servidor con streaming

convertToModelMessages

UIMessage[] a ModelMessage[]

createUIMessageStreamResponse

Respuesta HTTP con streaming

toUIMessageStream

Stream del resultado a protocolo UI

useChat

messages, sendMessage, status, error

sendMessage

sendMessage({ text: input })

Tip: Gateway Primero, Provider Después

Empezá con los IDs de modelo string del AI Gateway y una sola API key. Cambiá a un provider directo solo cuando necesites opciones específicas — tu código de ruta casi no cambia.

4. Los Dos Archivos, Explicados

Dos archivos sostienen toda la funcionalidad. Leelos una vez y nunca más vas a copiar código de chatbot a ciegas — cada línea viene del quickstart oficial v7.

🖥️ Servidor: app/api/chat/route.ts

POST más UIMessage[]

Parseá { messages } del body del request. El historial le da contexto al modelo — sin base de datos para sesiones simples.

streamText

Llamalo con un ID de modelo y los mensajes convertidos. Tools y stopWhen entran después; la forma del streaming no cambia.

convertToModelMessages

El puente entre tipos de UI y tipos del modelo. Si lo omitís, el modelo se atraganta con metadata que nunca pidió.

toUIMessageStream

Convierte result.stream al protocolo UI-message que el hook entiende del otro lado.

maxDuration

Exportá maxDuration = 30 para que la plataforma no mate generaciones largas a mitad del stream.

💬 Cliente: app/page.tsx

useChat más sendMessage

El hook apunta a POST /api/chat por defecto. Un useState local guarda el input; sendMessage({ text: input }) dispara el round trip.

message.parts

Renderizá partes, nunca un string content. Hacé switch sobre part.type para que texto, tools y reasoning tengan su UI.

use client

La página debe ser un client component — la interactividad del streaming necesita JavaScript del navegador.

Status más error

El hook expone status y error. Conectalos a un indicador de escritura y un botón de reintento antes de publicar.

Código completo del servidor — app/api/chat/route.ts

import {
  streamText,
  UIMessage,
  convertToModelMessages,
  createUIMessageStreamResponse,
  toUIMessageStream,
} from "ai";

export const maxDuration = 30;

export async function POST(req: Request) {
  const { messages }: { messages: UIMessage[] } = await req.json();

  const result = streamText({
    model: "xai/grok-4.6",
    messages: await convertToModelMessages(messages),
  });

  return createUIMessageStreamResponse({
    stream: toUIMessageStream({ stream: result.stream }),
  });
}

Código completo del cliente — app/page.tsx

"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 (
    <div>
      {messages.map((message) => (
        <div key={message.id}>
          {message.role === "user" ? "User: " : "AI: "}
          {message.parts.map((part, i) => {
            if (part.type === "text") {
              return <div key={message.id + "-" + i}>{part.text}</div>;
            }
            return null;
          })}
        </div>
      ))}
      <form
        onSubmit={(e) => {
          e.preventDefault();
          sendMessage({ text: input });
          setInput("");
        }}
      >
        <input
          value={input}
          placeholder="Say something..."
          onChange={(e) => setInput(e.currentTarget.value)}
        />
      </form>
    </div>
  );
}

¿Por qué no un fetch manual más useState?

Porque reimplementarías cancelación, batching de renders, recuperación de errores y el protocolo SSE a mano. useChat ya resuelve las cuatro — tu código queda en la capa de producto.

5. Errores Comunes (Todos de Migraciones Reales)

El SDK trajo cambios breaking en cada major desde v3, así que la mayoría de los tutoriales de 2024 hoy enseñan APIs eliminadas. Esta es la lista de correcciones que aplico al portar código viejo.

🪝

Importar useChat desde ai/react

Ese path se eliminó en v5 y nada compila. Importá { useChat } desde @ai-sdk/react — verificá la línea de import antes que nada.

📦

Renderizar message.content

Los mensajes exponen un array parts. Mapeá message.parts y hacé switch sobre part.type — las partes de texto traen part.text en orden.

🌊

Devolver toDataStreamResponse

Los helpers de transporte de la era v4 ya no existen. Devolvé createUIMessageStreamResponse con toUIMessageStream({ stream: result.stream }).

🔑

Publicar /api/chat sin autenticación

Sin chequeo de sesión ni rate limit, cualquiera que encuentre la URL gasta tu presupuesto. Agregá ambas antes de cualquier deploy público.

⏱️

Omitir maxDuration

Los timeouts por defecto pueden cortar streams largos a mitad de frase. Exportá maxDuration = 30 en el archivo de ruta y listo.

6. Checklist de Producción

Que funcione en local es el paso uno. Llegar a confiable a escala exige recorrer esta lista en orden:

✅ Antes de Publicar

  1. 1. API key en .env.local, nunca commiteada, validada al arrancar
  2. 2. La UI renderiza message.parts con texto y estados de tools, nunca .content
  3. 3. Indicador de escritura desde status del hook, botón de reintento desde error
  4. 4. Rate limit más auth en /api/chat antes de cualquier URL pública

📈 A Escala

  1. Timeouts: maxDuration = 30 en la ruta; reintentos con backoff o fallbacks del Gateway
  2. Persistencia: useChat es solo memoria — guardá los chats en Postgres o Supabase para historial entre sesiones
  3. Resume: Agregá streams reanudables para que recargar la página nunca pierda una generación en curso

🔁 Siguiente Nivel

  1. Tools: Agregá una primera tool con stopWhen para respuestas multi-paso con datos en vivo
  2. RAG: Embeddé la pregunta, recuperá chunks, inyectalos como contexto — el mismo pipeline de streaming
  3. Template: ¿Necesitás auth más persistencia más multimodal ya? Cloná el template oficial de chatbot

7. Cuándo Aplica Este Stack (y Cuándo No)

La capa UI del AI SDK es el camino más rápido al chat con streaming, pero no es para toda app. Calibrá antes de comprometerte:

✅ Usá AI SDK UI

  • Next.js App Router más UI de chat en React con streaming de tokens
  • Un provider hoy, quizás multi-provider mañana (el Gateway hace trivial el cambio)
  • Tools y agentes multi-paso como próximo hito natural
  • Prototipos que deben verse production-grade en una tarde
  • Equipos que quieren streaming, cancelación y errores resueltos — no hechos a mano

❌ Mejor evitalo

  • Apps legacy en Pages Router — el quickstart apunta a App Router
  • Backends no-Node (Django, Rails, Go): usá patrones Core, no el código de ruta de Next.js
  • Chatbots zero-code — cloná el starter template en vez de construir
  • Agentes background de larga duración — usá Workflows o Sandbox, no un hook de chat
  • Presupuestos de tokens estrictos sin control del backend — primero auth y límites

Regla de oro

Si los tokens no aparecen en pantalla unos 300ms después del envío, el bug está en tu transporte, no en tu modelo. Verificá que la ruta devuelva un stream real, que el cliente renderice partes y que nada acumule en el medio.

Conclusión

Ahora dominás todo el pipeline: UIMessage[] sale del navegador, convertToModelMessages lo adapta, streamText genera tokens, el stream de UI-message los trae de vuelta y useChat renderiza las partes en vivo. Dos archivos, sin WebSockets, sin parsear SSE a mano.

Publicá los seis pasos, aplicá el trío de endurecimiento (maxDuration, rate limit, auth) y corregí los cinco errores de migración si estás portando un tutorial viejo. Desde ahí, tools, persistencia y RAG se montan sobre la misma base — ese es el verdadero rédito de aprender la API actual en vez de copiar código de 2024.

Lo Que Construiste: Resumen

Servidor

  • • app/api/chat/route.ts
  • • pipeline de streamText
  • • maxDuration = 30

Cliente

  • • useChat + sendMessage
  • • renderizado de message.parts
  • • estados status + error

Próximos Pasos

  • • Primera tool + stopWhen
  • • Persistencia del chat
  • • Retrieval con RAG

Fuentes

Cada nombre de API y patrón de arriba está verificado contra estas referencias (agosto–septiembre 2026):

Diego Rodriguez

Diego Rodriguez

Ingeniero Senior Full-Stack & AI

Diego tiene mas de 9 anos de experiencia construyendo aplicaciones potenciadas por IA de produccion, desde orquestacion de LLMs y pipelines RAG hasta deteccion de riesgos con ML y sistemas de trading algoritmico.

Conoce mas sobre Diego