effidevFlutter · Cloudflare edge · Cloud cost optimization
English

Building Real-Time Streaming AI UI and Micro-Interactions in React / Next.js with Vercel AI SDK v3

React Next.js Streaming AI UI Vercel AI SDK Guide

Modern users expect ChatGPT-like real-time streaming text responses and dynamic Generative UI (interactive charts, cards, and widgets) rather than static loading spinners.

In React and Next.js App Router environments, the Vercel AI SDK (v3/v4) is the industry standard toolkit for building generative AI interfaces.

This article details using useChat, streamText, Tool Calling with zod schemas, and micro-interactions to prevent layout shifts during real-time streaming.

Key Takeaways

  • Streaming UI Benefit: Cuts perceived latency (TTFT) by over 80% by streaming response tokens immediately.
  • useChat & streamText: Synchronizes client-side React state with backend AI model streams over HTTP Data Streams without boilerplate.
  • Generative UI & Tool Calling: Render dynamic React components (charts, forms) directly when the LLM invokes structured tools.

1. Server Route Handler (app/api/chat/route.ts)

import { openai } from '@ai-sdk/openai';
import { streamText, tool } from 'ai';
import { z } from 'zod';

export const runtime = 'edge';

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

  const result = streamText({
    model: openai('gpt-4o-mini'),
    messages,
    tools: {
      getStockPrice: tool({
        description: 'Fetch stock price and chart data',
        parameters: z.object({ ticker: z.string() }),
        execute: async ({ ticker }) => ({ ticker, price: 215.5, change: '+3.2%' }),
      }),
    },
  });

  return result.toDataStreamResponse();
}

2. Client Component (app/chat/page.tsx)

'use client';

import { useChat } from 'ai/react';

export default function ChatPage() {
  const { messages, input, handleInputChange, handleSubmit } = useChat();

  return (
    <div className="p-4 max-w-xl mx-auto">
      {messages.map((m) => (
        <div key={m.id} className="py-2">
          <strong>{m.role}: </strong>{m.content}
        </div>
      ))}
      <form onSubmit={handleSubmit} className="flex gap-2 mt-4">
        <input value={input} onChange={handleInputChange} className="border p-2 flex-1" />
        <button type="submit" className="bg-indigo-600 text-white px-4 py-2">Send</button>
      </form>
    </div>
  );
}