Construye tu primera Generative UI con Vercel AI SDK
Guía paso a paso para crear tu primera interfaz de IA con componentes en streaming.
Prerrequisitos
Antes de empezar, asegúrate de tener:
- Node.js 18+
- Un proyecto Next.js 14+ con App Router
- Una clave de API de OpenAI (o Anthropic — el SDK es compatible con ambos)
- Conocimientos básicos de React Server Components
Si eres nuevo en RSC, dedica 15 minutos a la documentación oficial de Next.js sobre Server Components. La función streamUI del Vercel AI SDK está construida sobre RSC, y todo encajará en su lugar en cuanto entiendas ese modelo.
Qué vamos a construir
Crearemos un asistente de IA sencillo que genera interfaces interactivas a partir de los prompts del usuario. Al terminar este tutorial tendrás una funcionalidad de Generative UI en funcionamiento que:
- Recibe un prompt de texto del usuario
- Transmite componentes React desde el servidor mediante streaming
- Renderiza tarjetas y gráficos interactivos según las decisiones de la IA
Para el ejemplo usaremos un asistente financiero capaz de mostrar cotizaciones de acciones y datos meteorológicos — lo suficientemente simple para entenderlo rápido, y lo suficientemente complejo para mostrar patrones reales.
⚠️ AI SDK RSC y
streamUIestán marcados por Vercel como experimental. Para proyectos de producción, Vercel recomienda AI SDK UI (useChatde@ai-sdk/react). Este artículo muestra el patrón funcional de streaming con RSC para prototipos, demos y entornos controlados; para producción evalúa los trade-offs y consulta la sección «Cuándo Vercel AI SDK NO es tu opción». Guía de migración RSC → UI.
Paso 1: Instalar dependencias
npm install ai@^4 @ai-sdk/openai@^1 zod
Fijamos v4 — la última serie con la API RSC en formato parameters: e importación desde ai/rsc. Para v5+ consulta la nota sobre diferencias al final del artículo.
El paquete ai es el núcleo del Vercel AI SDK. @ai-sdk/openai es el proveedor de OpenAI (sustitúyelo por @ai-sdk/anthropic si prefieres Claude). zod se encarga de validar los parámetros de las herramientas — con él describes qué parámetros puede pasar la IA a cada componente.
Añade la clave de API a .env.local:
OPENAI_API_KEY=sk-...
Paso 2: Crear la biblioteca de componentes
Define los componentes que la IA podrá generar. Son componentes React normales — sin nada específico de IA. El principio de diseño clave: crea componentes útiles por sí mismos, y la IA podrá combinarlos libremente.
// components/weather-card.tsx
interface WeatherCardProps {
city: string;
temperature: number;
conditions: string;
humidity: number;
}
export function WeatherCard({ city, temperature, conditions, humidity }: WeatherCardProps) {
return (
<div className="rounded-lg border bg-card p-6 shadow-sm">
<h3 className="text-lg font-semibold">{city}</h3>
<div className="mt-2 flex items-baseline gap-2">
<span className="text-4xl font-bold">{temperature}°C</span>
<span className="text-muted-foreground">{conditions}</span>
</div>
<p className="mt-2 text-sm text-muted-foreground">
Humidity: {humidity}%
</p>
</div>
);
}
// components/stock-ticker.tsx
interface StockTickerProps {
symbol: string;
price: number;
change: number;
changePercent: number;
}
export function StockTicker({ symbol, price, change, changePercent }: StockTickerProps) {
const isPositive = change >= 0;
const sign = isPositive ? '+' : '';
const color = isPositive ? 'text-green-600' : 'text-red-600';
return (
<div className="rounded-lg border bg-card p-6 shadow-sm">
<div className="flex items-center justify-between">
<h3 className="text-xl font-bold">{symbol}</h3>
<span className={`text-sm font-medium ${color}`}>
{sign}{changePercent.toFixed(2)}%
</span>
</div>
<div className="mt-2 flex items-baseline gap-2">
<span className="text-3xl font-bold">${price.toFixed(2)}</span>
<span className={`text-sm ${color}`}>
{sign}{change.toFixed(2)} today
</span>
</div>
</div>
);
}
// components/loading-skeleton.tsx
export function CardSkeleton({ height = 'h-32' }: { height?: string }) {
return (
<div className={`animate-pulse rounded-lg bg-muted ${height} w-full`} />
);
}
Paso 3: Definir las herramientas de IA (Server Action)
Este es el corazón de Generative UI. Crea un server action que vincule tus componentes con la IA en forma de "herramientas" — funciones que el modelo puede invocar por su propia decisión:
// app/actions.tsx
'use server';
export const runtime = 'edge';
import { streamUI } from 'ai/rsc';
import { openai } from '@ai-sdk/openai';
import { z } from 'zod';
import { WeatherCard } from '@/components/weather-card';
import { StockTicker } from '@/components/stock-ticker';
import { CardSkeleton } from '@/components/loading-skeleton';
export async function generateUI(prompt: string) {
const result = await streamUI({
model: openai('gpt-4o'),
system: `You are a helpful financial and information assistant.
Use the available tools to display information visually
whenever possible. Prefer showing components over text responses.
When asked about weather or stocks, always use the appropriate tool.`,
prompt,
tools: {
showWeather: {
description: 'Display current weather conditions for a city. Use this when the user asks about weather, temperature, or climate.',
parameters: z.object({
city: z.string().describe('The city name, e.g. "Paris" or "New York"'),
temperature: z.number().describe('Current temperature in Celsius'),
conditions: z.string().describe('Weather description, e.g. "Partly cloudy"'),
humidity: z.number().min(0).max(100).describe('Relative humidity percentage'),
}),
generate: async function* (params) {
// Yield a skeleton immediately while data "loads"
yield <CardSkeleton height="h-36" />;
// In a real app, you would fetch live weather data here
return <WeatherCard {...params} />;
},
},
showStock: {
description: 'Display a stock price and daily change. Use this when the user asks about stock prices, market data, or a company\'s shares.',
parameters: z.object({
symbol: z.string().describe('Stock ticker symbol, e.g. "AAPL" or "TSLA"'),
price: z.number().describe('Current stock price in USD'),
change: z.number().describe('Price change today in USD'),
changePercent: z.number().describe('Percentage price change today'),
}),
generate: async function* (params) {
yield <CardSkeleton height="h-32" />;
return <StockTicker {...params} />;
},
},
},
});
return result.value;
}
Hay tres cosas importantes que entender en este código:
La función generate es un generador asíncrono. La palabra clave yield envía el skeleton inmediatamente — antes de que la IA termine de determinar los parámetros. return entrega el componente final. Así funciona el streaming en Generative UI.
Las descripciones de las herramientas son instrucciones para la IA. Los campos description son lo que el modelo lee para decidir qué herramienta invocar. Escríbelos con claridad, indicando cuándo se debe usar cada herramienta y cuándo no.
Los esquemas Zod fijan el contrato. Si defines esquemas Zod estrictos, la IA no podrá pasar parámetros inválidos. Los errores de validación se capturan antes del renderizado del componente.
Paso 4: Construir la interfaz
// app/page.tsx
'use client';
import { useState } from 'react';
import { generateUI } from './actions';
const EXAMPLE_PROMPTS = [
"What's the weather like in Tokyo?",
"Show me Apple's current stock price",
"Compare the weather in London and New York",
"How is Tesla stock doing?",
];
export default function Home() {
const [prompt, setPrompt] = useState('');
const [messages, setMessages] = useState<Array<{ prompt: string; ui: React.ReactNode }>>([]);
const [loading, setLoading] = useState(false);
async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
if (!prompt.trim() || loading) return;
const currentPrompt = prompt;
setPrompt('');
setLoading(true);
const ui = await generateUI(currentPrompt);
setMessages(prev => [...prev, { prompt: currentPrompt, ui }]);
setLoading(false);
}
return (
<main className="mx-auto max-w-2xl p-8">
<h1 className="text-3xl font-bold">Generative UI Demo</h1>
<p className="mt-2 text-muted-foreground">
Ask about weather or stocks — watch the AI generate the right interface.
</p>
{/* Example prompts */}
<div className="mt-4 flex flex-wrap gap-2">
{EXAMPLE_PROMPTS.map(p => (
<button
key={p}
onClick={() => setPrompt(p)}
className="rounded-full border px-3 py-1 text-sm hover:bg-muted"
>
{p}
</button>
))}
</div>
{/* Prompt input */}
<form onSubmit={handleSubmit} className="mt-6 flex gap-2">
<input
value={prompt}
onChange={e => setPrompt(e.target.value)}
placeholder="Ask anything..."
className="flex-1 rounded-md border bg-background px-4 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
/>
<button
type="submit"
disabled={loading || !prompt.trim()}
className="rounded-md bg-primary px-4 py-2 text-sm text-primary-foreground disabled:opacity-50"
>
{loading ? 'Generating...' : 'Ask'}
</button>
</form>
{/* Generated UI output */}
<div className="mt-8 space-y-6">
{messages.map((msg, i) => (
<div key={i}>
<p className="mb-2 text-sm font-medium text-muted-foreground">
"{msg.prompt}"
</p>
{msg.ui}
</div>
))}
</div>
</main>
);
}
Paso 5: Ejecutar y probar
npm run dev
Prueba estos prompts en orden para ver comportamientos distintos:
- "What's the weather in Paris?" — una sola tarjeta WeatherCard
- "Show me Apple stock" — un ticker StockTicker
- "Compare the weather in London and New York" — la IA invoca
showWeatherdos veces y genera dos tarjetas juntas - "How's Tesla doing and what's the weather in San Francisco?" — la IA invoca ambas herramientas y genera componentes de tipos distintos
El tercer prompt es la demostración clave: sin ningún código adicional, el modelo combina varios componentes por sí solo para responder a una pregunta compuesta.
Qué ocurre bajo el capó
Cuando envías un prompt:
- El cliente invoca el server action
generateUI streamUIenvía el prompt y las definiciones de herramientas a la API de OpenAI- El modelo decide qué herramientas invocar y con qué parámetros
- La función
generatede cada herramienta entrega el skeleton inmediatamente - La IA termina de determinar los parámetros y se devuelve el componente final
- React reemplaza el skeleton por el componente listo
El protocolo de streaming RSC es lo que hace posible todo esto. El servidor serializa árboles de componentes React y los transmite al cliente en partes. Esto es fundamentalmente diferente de una API JSON — el cliente recibe componentes listos, no datos en bruto.
Manejo de errores
Los componentes generados pueden fallar donde los escritos a mano no lo harían. Es importante entender: un React error boundary solo captura errores de renderizado. Un fallo del stream (pérdida de red, timeout de OpenAI, error del tool call en el servidor) no lo capturará — ese escenario hay que manejarlo explícitamente en handleSubmit.
Protección en dos capas — try/catch alrededor del stream y error boundary alrededor del UI renderizado:
// components/genui-error-boundary.tsx
'use client';
import { Component, ReactNode } from 'react';
interface Props { children: ReactNode; fallback?: ReactNode }
interface State { hasError: boolean; error: Error | null }
export class GenUIErrorBoundary extends Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = { hasError: false, error: null };
}
static getDerivedStateFromError(error: Error) {
return { hasError: true, error };
}
render() {
if (this.state.hasError) {
return this.props.fallback ?? (
<div className="rounded-lg border border-destructive/50 bg-destructive/5 p-4">
<p className="text-sm text-destructive">
This component could not render. The AI may have passed unexpected data.
</p>
</div>
);
}
return this.props.children;
}
}
Y los errores del propio stream se capturan en el manejador del cliente:
async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
if (!prompt.trim() || loading) return;
const currentPrompt = prompt;
setPrompt('');
setLoading(true);
try {
const ui = await generateUI(currentPrompt);
setMessages(prev => [...prev, { prompt: currentPrompt, ui }]);
} catch (err) {
// Errores de red, timeouts de OpenAI, fallo del server action — todo llega aquí
setMessages(prev => [...prev, {
prompt: currentPrompt,
ui: <div className="text-sm text-destructive">Stream failed. Try again.</div>,
}]);
} finally {
setLoading(false);
}
}
Envuelve el UI generado con GenUIErrorBoundary en la página — capturará errores de renderizado, y el try/catch se encarga del resto.
Cuándo Vercel AI SDK NO es tu opción
El SDK es bueno, pero no es la panacea. No lo uses si:
- El SDK está marcado como experimental — limitaciones documentadas: imposible interrumpir el stream a través de server actions, los componentes se remontan en
.done()(parpadeo), muchos<Suspense>boundaries pueden hacer crashear la página,createStreamableUIgenera un volumen cuadrático de datos. Para producción, Vercel recomienda AI SDK UI. - No usas Next.js.
streamUIestá construido sobre React Server Components, que requieren Next.js App Router (o Waku u otro framework RSC). Para SPA con Vite, Remix sin RSC, Vue, Svelte, Angular — consulta las alternativas abajo. - Necesitas un UI fijo con datos dinámicos. Si la interfaz está definida de antemano y el LLM solo se necesita para los datos, usa
generateObjectnormal + tu código React estático. Generative UI tiene sentido cuando la IA decide qué componentes mostrar. - Requisitos estrictos de privacidad o despliegue on-prem. El SDK depende de proveedores (OpenAI, Anthropic). Para LLM self-hosted es más sencillo escribir una capa delgada sobre
vLLM/Ollamay tu propio registro de componentes. - Colaboración en tiempo real o multijugador. El stream RSC es unidireccional. Para sincronización bidireccional de UI entre usuarios se necesitan soluciones WebSocket, no RSC.
- El presupuesto de tokens es crítico. Cada renderizado implica una llamada al LLM. Con MAU > 10k sin caché agresiva, la factura de gpt-4o puede superar $1k/mes.
Alternativas para proyectos que no usan Next.js
| Herramienta | Stack | Cuándo usarla |
|---|---|---|
| Thesys C1 API | Cualquiera (HTTP API) | SaaS, devuelve bloques de UI listos según esquema JSON. Ideal para equipos sin experiencia en RSC. |
| CopilotKit | React (Next.js + Vite + Remix) | Si necesitas copilotos in-app con estado y acciones. Soporta Generative UI vía useCopilotAction. |
| Tambo | React (universal) | Catálogo de componentes como entidad de primer nivel. Funciona con Vite, no requiere RSC. |
| A2UI | Cualquiera (Google) | Formato JSON declarativo de UI de Google para agentes. Independiente del renderer, funciona en cualquier frontend. |
| assistant-ui | React | Biblioteca chat-first con soporte de tool UIs. Buena base para copilotos en cualquier app React. |
| Capa propia | Cualquiera | Si necesitas 2–3 tipos de componentes y el control es crítico — registro + generateObject + un switch en el cliente ocupa ~150 líneas. |
Para Vue/Svelte/Angular, a fecha de mayo 2026 no hay soluciones de producción al nivel del Vercel AI SDK — la mayoría de equipos hacen un cliente delgado a una API que devuelve una descripción JSON del componente y renderizan en el frontend por su cuenta.
Despliegue en plataformas económicas
Vercel es la opción obvia para Next.js, pero no la única. Si el presupuesto es ajustado o no quieres depender de Vercel:
- Fly.io — $0–5/mes en planes hobby. Soporta Next.js vía Dockerfile. Regiones edge en todo el mundo. Límite del free tier — 3 máquinas × 256MB.
- Render — el web service gratuito entra en reposo tras 15 min de inactividad (primera solicitud tras el reposo ~30 seg). Adecuado para demos y proyectos personales, no para producción.
- Railway — $5 de créditos al mes en el plan hobby. Despliegue sencillo desde GitHub, buen DX, pero más caro que Fly.io al crecer.
- Cloudflare Pages + Workers — gratuito hasta 100k solicitudes/día. Requiere adaptación al runtime
nodejs_compat; el streaming RSC funciona con matices. - VPS propio + Coolify/Dokploy — desde $5/mes (Hetzner, Contabo). Control total, pero tú te encargas de las actualizaciones, SSL y monitorización.
Para la mayoría de proyectos personales, Fly.io ofrece el mejor equilibrio: inicio gratuito, camino razonable hacia producción, regiones edge sin vendor lock-in.
Qué necesita el equipo
Antes de llevar Vercel AI SDK a producción, evalúa la preparación del stack y del equipo:
Habilidades imprescindibles:
- React Server Components — sin esto,
streamUIserá una caja negra al primer bug. - TypeScript — los esquemas Zod y los parámetros de las herramientas sin tipos se convierten en caos.
- Generadores asíncronos (
async function*,yield) — no todo desarrollador React de nivel medio ha trabajado con ellos. - Prompt engineering — las descripciones de herramientas y el system prompt determinan la calidad de la selección de componentes. Es una disciplina aparte.
Habilidades deseables:
- Experiencia con LLM API (rate limits, estrategias de retry, contabilidad de tokens).
- Comprensión del Edge runtime y sus limitaciones (sin Node.js APIs, límite de tamaño del bundle).
- Observabilidad — logs estructurados de tool calls, trazado de solicitudes.
Tamaño del equipo y TCO (estimación, mayo 2026):
| Tamaño | Tiempo de ingeniería | Coste LLM (MAU 1k) | Coste LLM (MAU 10k) | ¿Viable? |
|---|---|---|---|---|
| Solo (1 persona) | 2–3 semanas para MVP | ~$50/mes (gpt-4o-mini) | ~$500/mes | Sí, para side-project |
| Pequeño (2–4) | 4–6 semanas para v1 | ~$150/mes (mix gpt-4o) | ~$1.500/mes | Sí, caso de uso principal |
| Mediano (5–15) | 2–3 meses para integración completa | ~$300/mes | ~$3k–5k/mes | Sí, si existe plataforma |
| Grande (15+) | 4–6 meses + equipo de plataforma | presupuesto negociado | $10k+/mes | Vale la pena evaluar LLM self-hosted |
Los números de LLM corresponden al escenario "1 solicitud por sesión, gpt-4o para selección de herramientas, gpt-4o-mini para parámetros". Los costes reales dependen mucho de la longitud de los prompts, la frecuencia de solicitudes repetidas y la estrategia de caché.
Metodología de cálculo del TCO: cifras calculadas bajo los supuestos: prompt medio ~800 input + ~300 output tokens en gpt-4o (o ~$0,001 en gpt-4o-mini), 1 solicitud/sesión, precios de OpenAI a mayo 2026, MAU ≈ DAU × 30%. Calibra según tu workload.
Consejos de despliegue
Variables de entorno: OPENAI_API_KEY debe estar disponible en tu entorno de producción. En Vercel, añádela en la configuración del proyecto en la sección Environment Variables.
Edge runtime: la función streamUI funciona en Edge runtime, lo que reduce significativamente el tiempo de cold start. Añade export const runtime = 'edge' en el archivo del server action.
Rate limiting: sin limitación de frecuencia, un solo usuario puede generar miles de solicitudes a la IA. Añade un rate limiter antes de invocar streamUI. El paquete @upstash/ratelimit se integra bien con Next.js.
Selección del modelo: gpt-4o da los mejores resultados en la selección de componentes, pero es más caro. gpt-4o-mini cuesta unas 15× menos en input/output (openai.com/api/pricing, 2026-05) y se desenvuelve bien con conjuntos de componentes sencillos. Prueba ambos con tus definiciones de herramientas concretas.
Próximos pasos
En esta guía cubrimos los fundamentos. Para Generative UI en producción:
- Añade nuevas herramientas — cada nuevo componente en el registro amplía las capacidades de la IA
- Implementa caché de resultados — cachea las solicitudes frecuentes para reducir latencia y costes
- Añade texto en streaming junto con los componentes de UI para que la IA pueda explicar lo que muestra
- Usa structured outputs para una generación de parámetros más fiable
- Configura observabilidad — registra cada tool call, sus parámetros y las acciones del usuario
La documentación de Vercel AI SDK describe en detalle todos estos patrones, y el repositorio de ejemplos tiene plantillas listas para producción que merece la pena explorar.
Sobre AI SDK v5/v6
Si usas versiones más recientes del SDK, las diferencias clave respecto al código de este artículo son:
parameters:en la definición de la herramienta →inputSchema:- Import
import { streamUI } from 'ai/rsc'→import { streamUI } from '@ai-sdk/rsc' - RSC sigue marcado por Vercel como experimental — para producción se recomienda AI SDK UI (
useChat).
¿Quieres integrar Generative UI en tu producto? Hablamos de tu caso — por nuestra experiencia en consultoría, el stack GenUI encaja bien en dashboards y herramientas internas; para superficies reguladas y páginas públicas de alto tráfico los trade-offs generalmente no son favorables.
Divulgación: los enlaces externos a productos (Thesys, CopilotKit, Tambo, Vercel, Fly.io, Render, Railway, Cloudflare, Upstash) son recomendaciones orgánicas; no hay programas de afiliados ni publicidad. Precios actualizados a 2026-05-11.
Alex
Ingeniero y Consultor de Generative UI
Ingeniero senior especializado en interfaces con AI y sistemas Generative UI. Ayudando a equipos de producto a lanzar más rápido con el stack GenUI adecuado.
Artículos relacionados
Κατασκευάζοντας το Πρώτο σας Generative UI με το Vercel AI SDK
Βήμα-βήμα οδηγός για τη δημιουργία της πρώτης σας AI-powered διεπαφής με streaming συστατικά.
Προσβασιμότητα σε Generative UI: Δημιουργία Συμπεριληπτικών AI Διεπαφών
Πρακτικός οδηγός για προσβάσιμα γεννητικά interfaces — screen readers, πλοήγηση με πληκτρολόγιο και συνδυαστικά προβλήματα προσβασιμότητας.
CopilotKit vs Vercel AI SDK vs Thesys: Σύγκριση Frameworks
Μια ειλικρινής σύγκριση των τριών κύριων frameworks Generative UI, με πλεονεκτήματα, μειονεκτήματα και πότε να χρησιμοποιείτε το καθένα.
Adelántate en Generative UI
Artículos semanales, actualizaciones de frameworks y guías de implementación prácticas — directamente a tu bandeja de entrada.
¿Necesitas ayuda para implementar lo que acabas de leer?
Reserva una consulta gratuita