React / Next.js에서 Vercel AI SDK v3를 활용한 스트리밍 AI UI 및 마이크로 인터랙션 구현

기존 AI 웹 애플리케이션은 사용자가 질문을 던진 뒤 전체 응답이 완성될 때까지 로딩 스피너를 바라보며 기다리는 정적인 UX 방식이 주를 이루었습니다. 하지만 현대 사용자는 ChatGPT나 Claude와 같이 실시간으로 글자가 하나씩 생성되는 스트리밍 UX와, 단순 텍스트 답변을 넘어 차트·카드·버튼 등의 풍부한 UI 요소가 동적으로 생성되는 Generative UI를 기대합니다.
React 및 Next.js App Router 생태계에서 이러한 최첨단 AI 인터페이스를 구현하는 표준 툴킷이 바로 Vercel AI SDK (v3/v4)입니다.
이 글에서는 Vercel AI SDK의 핵심 API인 useChat, Server Actions 기반 streamUI, Structured Data Output, 그리고 텍스트 스트리밍 중 발생할 수 있는 layout shift를 막아주는 마이크로 인터랙션 및 프레임 최적화 기법을 완벽한 가이드로 제공합니다.
핵심 요약
- Streaming UI의 중요성: LLM 응답 대기 시간 동안 TTFT(Time to First Token)를 최소화하여 대기 체감 시간을 80% 이상 단축시킵니다.
useChat&streamText: HTTP Data Stream 프로토콜을 통해 클라이언트 React state와 백엔드 LLM 스트림을 보일러플레이트 없이 동기화합니다.- Generative UI (
streamUI): 단순 텍스트 응답이 아닌 React Server Component(RSC) 인터페이스 자체를 서버에서 생성하여 스트리밍 전달합니다.- Structured Outputs & Tool Calling:
zod스키마 기반으로 LLM이 도구(Tools)를 호출할 때 실시간 날씨 위젯, 주식 차트 컴포넌트를 UI에 동적으로 렌더링합니다.
1. Vercel AI SDK 아키텍처 흐름
[ Client Component (useChat) ]
│ (POST /api/chat - Message History)
▼
[ Next.js App Router (Route Handler / Server Action) ]
│ (streamText / streamUI + Tool Calling)
▼
[ AI Provider (OpenAI / Anthropic / Google Gemini) ]
│ (Server Sent Events / HTTP Data Stream)
▼
[ Client UI (Real-time Token Stream & React Server Components) ]
2. Server Route Handler 작성 (streamText & Tool Calling)
Next.js App Router의 Route Handler에서 LLM 호출 및 스트리밍 응답을 구성합니다.
// app/api/chat/route.ts
import { openai } from '@ai-sdk/openai';
import { streamText, tool } from 'ai';
import { z } from 'zod';
export const runtime = 'edge'; // Cloudflare Workers / Vercel Edge 지원
export async function POST(req: Request) {
const { messages } = await req.json();
const result = streamText({
model: openai('gpt-4o-mini'),
messages,
system: '당신은 기술 블로그 effidev의 친절한 AI 어시스턴트입니다.',
tools: {
// 주식 가격 조회 툴 정의 -> UI 차트로 자동 변환
getStockPrice: tool({
description: '지정된 주식 종목의 현재 가격과 차트 데이터를 조회합니다.',
parameters: z.object({
ticker: z.string().describe('주식 티커 기호 (예: AAPL, TSLA)'),
}),
execute: async ({ ticker }) => {
// 가상 주식 데이터 반환
return { ticker, price: 215.5, change: '+3.2%', history: [210, 212, 215.5] };
},
}),
},
});
// Client로 HTTP Data Stream 전송
return result.toDataStreamResponse();
}
3. Client Component 구현 (useChat 및 Generative UI)
Client 영역에서는 useChat 훅을 이용하여 메시지 히스토리 관리, 입력 제출, 스트리밍 상태 및 Tool Call 결과를 직관적으로 처리합니다.
// app/chat/page.tsx
'use client';
import { useChat } from 'ai/react';
export default function ChatPage() {
const { messages, input, handleInputChange, handleSubmit, isLoading } = useChat();
return (
<div className="max-w-2xl mx-auto p-4 flex flex-col h-screen">
<div className="flex-1 overflow-y-auto space-y-4">
{messages.map((m) => (
<div
key={m.id}
className={`p-4 rounded-xl ${
m.role === 'user' ? 'bg-indigo-600 text-white ml-auto max-w-[80%]' : 'bg-gray-100 text-gray-900 mr-auto max-w-[80%]'
}`}
>
<div className="font-semibold text-xs mb-1">{m.role === 'user' ? 'You' : 'AI'}</div>
<p className="whitespace-pre-wrap">{m.content}</p>
{/* Tool Calls 결과 처리: 주식 차트 컴포넌트 렌더링 */}
{m.toolInvocations?.map((toolInvocation) => {
const toolCallId = toolInvocation.toolCallId;
if (toolInvocation.toolName === 'getStockPrice') {
if ('result' in toolInvocation) {
const { ticker, price, change } = toolInvocation.result;
return (
<div key={toolCallId} className="mt-3 p-3 bg-white text-gray-900 rounded-lg shadow-sm border">
<div className="font-bold">{ticker} Stock Info</div>
<div className="text-xl font-bold text-green-600">${price} ({change})</div>
</div>
);
}
return <div key={toolCallId} className="mt-2 text-xs text-gray-500 animate-pulse">주식 정보 조회 중...</div>;
}
return null;
})}
</div>
))}
</div>
<form onSubmit={handleSubmit} className="mt-4 flex gap-2">
<input
value={input}
onChange={handleInputChange}
placeholder="질문을 입력하세요..."
className="flex-1 p-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500"
/>
<button
type="submit"
disabled={isLoading}
className="px-6 py-3 bg-indigo-600 text-white font-medium rounded-lg hover:bg-indigo-700 disabled:opacity-50"
>
전송
</button>
</form>
</div>
);
}
4. AI 스트리밍 UI 비교 분석표
| 기능 | 전통적 REST API 방식 | SSE (Server-Sent Events) | Vercel AI SDK v3/v4 |
|---|---|---|---|
| 응답 체감 Latency | 느림 (전체 완료 대기) | 보통 (텍스트 전용) | 매우 빠름 (첫 토큰 즉시) |
| Client 상태 관리 | state 직접 관리 필요 | EventSource 파싱 필요 | useChat 하나로 자동 처리 |
| Generative UI 지원 | 불가능 | 텍스트만 가능 | RSC & Tool Call 동적 UI 지원 |
| Edge Network 배포 | 제한적 | 구현 복잡 | Cloudflare / Vercel Edge 지원 |
5. UI 마이크로 인터랙션 및 Layout Shift 방지 팁
- Skeleton & Pulse Loading: Tool Call 호출 상태일 때 고정 높이의 Pulse 스켈레톤을 노출하여 Layout Shift(CLS)를 예방합니다.
- Smooth Auto-Scroll: 텍스트 스트리밍 중 사용자가 위로 스크롤한 경우에는 자동 스크롤을 일시 정지하고, 최하단 위치일 때만
scrollIntoView({ behavior: 'smooth' })를 실행합니다.
Vercel AI SDK를 도입하면 단순한 텍스트 기반 대화창을 넘어 풍부한 인터랙티브 컴포넌트가 동적으로 생성되는 차세대 AI 웹 앱을 신속하게 구현할 수 있습니다.