מדריך

בניית Generative UI ראשון עם Vercel AI SDK

מדריך שלב-אחר-שלב ליצירת ממשק ראשון מבוסס AI עם רכיבי סטרימינג.

A
Alex18 דקות קריאה

דרישות מוקדמות

לפני שמתחילים, ודאו שיש לכם:

  • Node.js 18+ מותקן
  • פרויקט Next.js 14+ שמשתמש ב-App Router
  • מפתח API של OpenAI (או Anthropic — ה-SDK תומך בשניהם)
  • היכרות בסיסית עם React Server Components

אם אתם חדשים ב-RSC, הקדישו 15 דקות לתיעוד Next.js בנושא Server Components לפני שממשיכים. הפונקציה streamUI של Vercel AI SDK מתבססת על RSC ותהיה הרבה יותר ברורה ברגע שתבינו את המודל.

מה בונים

נבנה עוזר AI פשוט שמייצר ממשק אינטראקטיבי בהתבסס על פרומפטים מהמשתמש. בסוף המדריך תהיה לכם פיצ'ר Generative UI עובד שעושה את הדברים הבאים:

⚠️ AI SDK RSC ו-streamUI מסומנים ניסיוניים על ידי Vercel. לפרויקטי ייצור Vercel ממליצה על AI SDK UI (‏useChat מ-@ai-sdk/react). מאמר זה מציג דפוס RSC streaming עובד לפרוטוטיפים, דמואים וסביבות מבוקרות; לייצור, שקלו את הפשרות וראו «מתי Vercel AI SDK אינו הבחירה שלכם». מדריך מיגרציה RSC → UI.

  1. קולט פרומפט טקסט מהמשתמש
  2. מסטרים רכיבי React בחזרה מהשרת
  3. מרנדר כרטיסים ותרשימים אינטראקטיביים בהתבסס על החלטות ה-AI

הדומיין לדוגמה הוא עוזר פיננסי שיכול להציג מחירי מניות ונתוני מזג אוויר — פשוט מספיק להבנה מהירה, מורכב מספיק כדי להדגים דפוסים אמיתיים.

שלב 1: התקנת תלויות

npm install ai@^4 @ai-sdk/openai@^1 zod

פינו v4 — הסדרה האחרונה עם ה-RSC API בצורת parameters: ו-import מ-ai/rsc. ל-v5+, ראו את ההערה על ההבדלים בסוף המאמר.

החבילה ai היא ליבת Vercel AI SDK. @ai-sdk/openai הוא ספק OpenAI (החליפו ב-@ai-sdk/anthropic אם אתם מעדיפים Claude). zod מטפל בולידציה של פרמטרי כלים — כך מגדירים אילו פרמטרים ה-AI יכול להעביר לכל רכיב.

הוסיפו את מפתח ה-API לקובץ .env.local:

OPENAI_API_KEY=sk-...

שלב 2: יצירת ספריית הרכיבים

הגדירו את הרכיבים שה-AI יכול לייצר. אלו רכיבי React רגילים לחלוטין — אין בהם שום דבר ייחודי ל-AI. עיקרון העיצוב המרכזי: בנו רכיבים שמועילים גם כשעומדים בפני עצמם, והם יהיו ניתנים להרכבה על ידי ה-AI.

// 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`} />
  );
}

שלב 3: הגדרת כלי AI (Server Action)

זהו הלב של Generative UI. צרו server action שמחבר את הרכיבים שלכם ל-AI בתור "כלים" — פונקציות שהמודל יכול להחליט לקרוא להן:

// app/actions.tsx
'use server';

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;
}

שלושה דברים שכדאי להבין על הקוד הזה:

הפונקציה generate היא async generator. מילת המפתח yield שולחת את ה-skeleton באופן מיידי — לפני שה-AI מסיים לפתור את הפרמטרים. ה-return שולח את הרכיב הסופי. כך עובד סטרימינג ב-Generative UI.

תיאורי הכלים הם הוראות ל-AI. שדות ה-description הם מה שהמודל קורא כדי להחליט איזה כלי לקרוא. כתבו אותם בבהירות, כולל מתי הכלי צריך ולא צריך להיות בשימוש.

סכמות Zod אוכפות את החוזה. ה-AI לא יכול להעביר פרמטרים לא תקינים אם הגדרתם סכמות Zod קפדניות. כשלי ולידציה נתפסים לפני שהרכיב מרונדר.

שלב 4: בניית הממשק

// 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>
  );
}

שלב 5: הרצה ובדיקה

npm run dev

נסו את הפרומפטים הבאים לפי הסדר כדי לראות התנהגויות שונות:

  • "What's the weather in Paris?" — כרטיס WeatherCard בודד
  • "Show me Apple stock" — StockTicker בודד
  • "Compare the weather in London and New York" — ה-AI קורא ל-showWeather פעמיים ומייצר שני כרטיסים זה לצד זה
  • "How's Tesla doing and what's the weather in San Francisco?" — ה-AI קורא לשני הכלים ומייצר סוגי רכיבים מעורבים

הפרומפט השלישי הוא ההדגמה המרכזית: ללא כל קוד נוסף, המודל מרכיב כמה רכיבים יחד כדי לענות על שאלה מורכבת.

מה קורה מאחורי הקלעים

כשמגישים פרומפט:

  1. הלקוח קורא ל-server action של generateUI
  2. streamUI שולח את הפרומפט + הגדרות הכלים ל-OpenAI API
  3. המודל בוחר אילו כלים לקרוא להם ועם אילו פרמטרים
  4. הפונקציה generate של כל כלי מייצרת skeleton באופן מיידי
  5. ה-AI מסיים לפתור את הפרמטרים, והרכיב הסופי מוחזר
  6. React מרנדר את הרכיב במקום ה-skeleton

פרוטוקול סטרימינג RSC הוא שמאפשר זאת לקרות. השרת ממיר עצי רכיבי React לנתונים ומסטרים אותם ללקוח בצורה מצטברת. זה שונה מ-JSON API — הלקוח מקבל רכיבים מרונדרים, לא נתונים גולמיים.

טיפול בשגיאות

רכיבים שנוצרו עלולים להיכשל בדרכים שרכיבים שנכתבו ידנית לא נכשלים. אבל יש דקויות שכדאי לזכור: Error boundaries של React תופסים רק שגיאות זמן-רינדור. כשל stream (נפילת רשת, timeout של OpenAI, שגיאת כלי בצד-שרת) לא ייתפס על ידי ה-boundary — צריך לטפל בזה במפורש ב-handleSubmit.

הגנה בשתי שכבות — try/catch מסביב ל-stream, ו-error boundary מסביב לממשק שרונדר:

// 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;
  }
}

וטפלו בשגיאות stream בצד-לקוח:

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) {
    // שגיאות רשת, timeout של OpenAI, כשלי server action — הכל מגיע לכאן
    setMessages(prev => [...prev, {
      prompt: currentPrompt,
      ui: <div className="text-sm text-destructive">Stream נכשל. נסו שוב.</div>,
    }]);
  } finally {
    setLoading(false);
  }
}

עטפו את הממשק שנוצר ב-GenUIErrorBoundary בדף — הוא מטפל בשגיאות רינדור, וה-try/catch מטפל בכל השאר.

מתי לא להשתמש ב-Vercel AI SDK {#when-not-to-use-vercel-ai-sdk}

ה-SDK מוצק, אבל הוא לא קסם. דלגו עליו אם:

  • ה-SDK מסומן ניסיוני — מגבלות מתועדות: streams לא ניתנים לביטול מ-server actions, רכיבים מתעדכנים מחדש על .done() (ריצוד), <Suspense> boundaries רבים יכולים לקרוס את הדף, createStreamableUI מייצר נפח העברה ריבועי. לייצור Vercel ממליצה על AI SDK UI.
  • אתם לא על Next.js. streamUI בנוי על React Server Components, שמצריכים Next.js App Router (או Waku / פריימוורק מודע-RSC אחר). לאפליקציות Vite SPA, Remix ללא RSC, Vue, Svelte, או Angular — ראו חלופות למטה.
  • צריכים ממשק קבוע עם נתונים דינמיים. אם הממשק ידוע מראש ו-LLM רק ממלא נתונים, השתמשו ב-generateObject רגיל + React סטטי. Generative UI משתלם כשה-AI מחליט אילו רכיבים להציג.
  • פרטיות קפדנית או דיפלוי on-prem. ה-SDK מניח ספקים מאוחסנים (OpenAI, Anthropic). ל-LLMs self-hosted, שכבה דקה מעל vLLM/Ollama בתוספת רג'יסטרי רכיבים משלכם פשוטה יותר.
  • תקציב tokens קריטי. כל רינדור הוא קריאת LLM. מעל MAU > 10k ללא cache אגרסיבי, עלויות gpt-4o יכולות לעבור 1,000$/חודש.

חלופות לפרויקטים שאינם Next.js

כלימחסניתמתי לבחור
Thesys C1 APIכל מחסנית (HTTP API)SaaS שמחזיר בלוקי ממשק מוכנים-לרינדור דרך סכמת JSON. מעולה לצוותים ללא ניסיון RSC.
CopilotKitReact (Next.js + Vite + Remix)Copilots בתוך אפליקציה עם מצב ופעולות. תומך ב-Generative UI דרך useCopilotAction.
TamboReact (universal)קטלוג רכיבים כמושג ראשי. עובד על Vite, ללא RSC.
A2UIכל מחסנית (Google)פורמט JSON UI דקלרטיבי של Google לסוכנים. renderer-agnostic, מצייר על כל frontend.
assistant-uiReactספרייה ממוקדת-צ'אט עם תמיכת tool-UI. בסיס איתן ל-copilots על כל אפליקציית React.
בנייה עצמיתכל מחסניתאם צריכים 2–3 סוגי רכיבים ושליטה חשובה — registry + generateObject + switch בצד-לקוח הם ~150 שורות.

לסטאקים Vue / Svelte / Angular נכון למאי 2026, אין מקבילה ברמת ייצור ל-Vercel AI SDK. רוב הצוותים שולחים client דק ל-API שמחזיר תיאור JSON של רכיב ומרנדרים אותו בעצמם.

דיפלוי על פלטפורמות עלות-נמוכה

Vercel הוא הבחירה הברורה ל-Next.js, אבל לא היחידה. אם תקציב חשוב או שרוצים להימנע מתלות בספק:

  • Fly.io — 0–5$/חודש בתוכניות hobby. Next.js דרך Dockerfile, אזורי edge ברחבי העולם. שכבה חינמית מוגבלת ל-3 מכונות × 256MB.
  • Render — שירותי web חינמיים נכנסים למצב שינה לאחר 15 דקות ללא פעילות (הבקשה הראשונה לאחר שינה לוקחת ~30 שניות). מתאים לדמואים ופרויקטי תחביב, לא לייצור.
  • Railway — 5$ קרדיט חודשי בתוכנית hobby. דיפלויים פשוטים מ-GitHub, DX מעולה, אבל יקר יותר מ-Fly.io בצמיחה.
  • Cloudflare Pages + Workers — חינם עד 100k בקשות/יום. צריך nodejs_compat runtime, RSC streaming עובד עם הסתייגויות.
  • VPS משלכם + Coolify / Dokploy — מ-5$/חודש (Hetzner, Contabo). שליטה מלאה, אתם אחראים לעדכונים, SSL, מוניטורינג.

לרוב פרויקטי תחביב Fly.io מוצא את האיזון הטוב ביותר: שכבה חינמית להתחלה, נתיב ייצור אמיתי, אזורי edge, ללא תלות בספק.

דרישות מיומנות צוות

לפני שמושכים את Vercel AI SDK לייצור, העריכו את מוכנות הצוות והמחסנית:

מיומנויות נדרשות:

  • React Server Components — ללא זה, streamUI הוא קופסה שחורה בבאג הראשון.
  • TypeScript — סכמות Zod ופרמטרי כלים מתדרדרים לבוץ ללא טיפוסים.
  • Async generators (‏async function*, yield) — לא כל מהנדס React ברמת אמצע השתמש בהם.
  • Prompt engineering — תיאורי הכלים ו-system prompt מגדירים את איכות בחירת הרכיבים. זו דיסציפלינה נפרדת.

מיומנויות שימושיות:

  • ניסיון עם LLM APIs (הגבלות קצב, אסטרטגיות retry, חשבונאות tokens).
  • היכרות עם edge runtime ומגבלותיה (ללא Node.js APIs, מגבלת גודל bundle).
  • Observability — לוגים מובנים של קריאות כלים, מעקב בקשות.

גודל צוות ו-TCO (גס, מאי 2026):

גודלזמן הנדסיעלות LLM (MAU 1k)עלות LLM (MAU 10k)ריאליסטי?
Solo (1)2–3 שבועות ל-MVP~50$/חודש (gpt-4o-mini)~500$/חודשכן, נקודת מתיקות לפרויקט-צד
קטן (2–4)4–6 שבועות ל-v1~150$/חודש (gpt-4o mix)~1,500$/חודשכן, תרחיש שימוש ראשי
בינוני (5–15)2–3 חודשים לאינטגרציה מלאה~300$/חודש~3,000–5,000$/חודשכן, אם קיימת פלטפורמה
גדול (15+)4–6 חודשים + צוות פלטפורמהתקציב מוסכם10,000+$/חודששווה לשקול LLM self-hosted

מספרי LLM מניחים "בקשה אחת ל-session, gpt-4o לבחירת כלים, gpt-4o-mini לפרמטרים." עלויות אמיתיות תלויות מאוד באורך ה-prompt, שיעור retry ואסטרטגיית caching.

מתודולוגיית TCO: מספרים חושבו תחת הנחות אלה — prompt ממוצע ~800 input + ~300 output tokens על gpt-4o (או ~0.001$ על gpt-4o-mini), בקשה אחת ל-session, תמחור OpenAI נכון ל-2026-05, MAU ≈ DAU × 30%. כיילו לפי העומס שלכם.

טיפים לדיפלוי

משתני סביבה: OPENAI_API_KEY חייב להיות זמין בסביבת הייצור שלכם. ב-Vercel, הוסיפו אותו בהגדרות הפרויקט תחת Environment Variables.

Edge runtime: הפונקציה streamUI עובדת על Edge runtime, מה שמפחית זמני אתחול בצורה משמעותית. הוסיפו export const runtime = 'edge' לקובץ ה-server action שלכם.

הגבלת קצב: ללא הגבלת קצב, משתמש בודד יכול לייצר אלפי בקשות AI. הוסיפו rate limiter לפני הקריאה ל-streamUI. החבילה @upstash/ratelimit משתלבת היטב עם Next.js.

בחירת מודל: gpt-4o מייצר את הבחירות הטובות ביותר ברכיבים אבל עולה יותר. gpt-4o-mini זול פי ~15 על input/output (‏openai.com/api/pricing, 2026-05) ועובד היטב עבור ערכות רכיבים פשוטות. בדקו את שניהם עם הגדרות הכלים הספציפיות שלכם.

צעדים הבאים

מדריך זה כיסה את היסודות. לצרכי Generative UI בסביבת ייצור:

  • הוסיפו כלים נוספים — כל רכיב חדש שתוסיפו לרג'יסטרי מרחיב את מה ש-AI יכול לענות עליו
  • ממשו cache לתוצאות כלים — שמרו שאילתות נפוצות כדי להפחית זמן תגובה ועלויות
  • הוסיפו סטרימינג טקסט לצד רכיבי ממשק כדי שה-AI יוכל להסביר מה הוא מציג
  • השתמשו ב-structured outputs לייצור פרמטרים אמין יותר
  • הגדירו observability — תעדו כל קריאה לכלי, פרמטריה, ואינטראקציות משתמש

תיעוד Vercel AI SDK מכסה את כל הדפוסים הללו לעומק, ומאגר הדוגמאות כולל תבניות starter ברמת ייצור שכדאי לבחון.

על AI SDK v5/v6

אם אתם משתמשים בגרסאות SDK חדשות יותר, ההבדלים המרכזיים מהקוד במאמר זה:

  • parameters: בהגדרת הכלי → inputSchema:
  • import { streamUI } from 'ai/rsc'import { streamUI } from '@ai-sdk/rsc'
  • RSC עדיין מסומן ניסיוני על ידי Vercel — לייצור, AI SDK UI (‏useChat) מומלץ.

רוצים לממש Generative UI במוצר שלכם? בואו נדון בתרחיש השימוש שלכם — לפי ניסיוננו בייעוץ, מחסנית ה-GenUI מתאימה היטב לדשבורדים וכלים פנימיים; למשטחים מוסדרים ודפים ציבוריים בתנועה גבוהה הפשרות בדרך כלל לא מצדיקות.

גילוי נאות: קישורים לפני מוצרים חיצוניים (Thesys, CopilotKit, Tambo, Vercel, Fly.io, Render, Railway, Cloudflare, Upstash) הם המלצות אורגניות; אין תוכניות שותפים, אין שיבוצים בתשלום. מחירים מדויקים נכון ל-2026-05-11.

שתףTwitterLinkedInאימייל
vercel-ai-sdkreacttutorialstreaming
A

Alex

מהנדס וייעוץ Generative UI

מהנדס בכיר המתמחה בממשקי AI ומערכות Generative UI. מסייע לצוותי מוצר לשלוח מהר יותר עם ה-stack הנכון.

הישארו קדימה ב-Generative UI

מאמרים שבועיים, עדכוני framework ומדריכי יישום מעשיים — ישירות לתיבת הדואר.

אנו מכבדים את פרטיותכם. ניתן להסיר את עצמכם בכל עת.

זקוקים לעזרה ביישום מה שקראתם?

קבעו ייעוץ חינם