정적 자산에서 동적 합성으로: Next.js에서 DALL‑E 3와 Vercel AI SDK 마스터하기
Source: Dev.to
번역할 텍스트가 제공되지 않았습니다. 번역을 원하는 본문을 알려주시면 한국어로 번역해 드리겠습니다.
Generative UI with DALL‑E 3 & the Vercel AI SDK
Imagine a web application where the visuals aren’t pre‑baked assets sitting on a CDN, but are synthesized in real‑time, tailored perfectly to the user’s imagination. This isn’t science‑fiction; it’s the reality of Generative UI. In this chapter we move beyond static image delivery and dive deep into the architecture of dynamic media synthesis using DALL‑E 3 and the Vercel AI SDK.
Why “Stream and Observe” Instead of “Request and Wait”?
If you’ve ever wondered how to integrate high‑latency AI generation into a snappy, responsive React interface, you’re in the right place. The goal is to transform the paradigm from:
- Request & Wait → Stream & Observe
At its heart, integrating DALL‑E 3 into a Next.js application is not merely about calling an API. It’s about treating the web‑development paradigm as a shift from static asset delivery to dynamic media synthesis.
From Static Images to Transient Computations
| Traditional Web (Grocery Store) | Generative UI (High‑End Restaurant) |
|---|---|
| Assets (images, CSS, JS) are pre‑packaged goods on shelves. | The image is a transient, stateful output of a computational process. |
| When a customer asks for an apple, the clerk picks one from the bin. | When a user requests an image, the chef (AI model) receives a prompt, gathers ingredients (latent noise), and begins synthesis (diffusion steps). |
| The apple is static; it was picked, washed, and shelved hours ago. | The dish is prepared on‑the‑fly, streamed back to the client as it becomes ready. |
The Vercel AI SDK acts as the Head Chef, managing communication between the waiter (client UI) and the line cooks (OpenAI API), ensuring the order is processed correctly and notifying the waiter immediately when the dish is ready to be served.
State Machine for Image Generation
When we invoke generateImage, we create a state machine that transitions through distinct phases. This is crucial for handling the asynchronous nature of DALL‑E 3 (which can take 10‑30 seconds).
- Idle – UI waits for user input.
- Processing – User clicks “Generate.” The RSC receives the request and initiates the stream.
- Generating – The AI model runs on the server. The SDK streams back tokens indicating progress.
- Ready – The server uploads the image to temporary storage (e.g., Vercel Blob) and returns a signed URL.
- Display – The React component hydrates, replacing the loading state with an
<img>tag.
This mirrors the ReAct Loop (Reasoning → Acting → Observing). The system reasons that the user wants a visual, acts by calling the tool, and observes the stream until the asset is ready.
Source: …
구현: Next.js와 Vercel AI SDK로 “키친” 만들기
아래는 두 개의 핵심 파일입니다:
- 서버 액션 – OpenAI에 대한 보안 API 호출을 처리하고 이미지 데이터를 관리합니다.
- 클라이언트 컴포넌트 – 사용자 입력을 관리하고 결과를 표시합니다.
1️⃣ 서버 액션 (app/actions/generateImage.ts)
// app/actions/generateImage.ts
'use server';
import { generateImage } from '@ai-sdk/openai';
import { openai } from '@ai-sdk/openai'; // Ensure you have the provider installed
export async function generateImageAction(prompt: string) {
try {
// 1️⃣ Select the Model
const model = openai.image('dall-e-3');
// 2️⃣ Call the AI SDK
const { image } = await generateImage({
model,
prompt,
size: '1024x1024',
quality: 'standard',
});
if (!image) return { error: 'No image data received.' };
// 3️⃣ Convert Binary to Base64 for immediate display
// Note: In a production SaaS (see advanced section), upload to Vercel Blob
// to avoid payload‑size limits.
const base64 = Buffer.from(image.uint8Array).toString('base64');
const dataUrl = `data:image/png;base64,${base64}`;
return { url: dataUrl };
} catch (error) {
console.error('Image generation error:', error);
return { error: 'Failed to generate image.' };
}
}
2️⃣ 클라이언트 컴포넌트 (app/page.tsx)
// app/page.tsx
'use client';
import { useState } from 'react';
import { generateImageAction } from './actions/generateImage';
export default function ImageGeneratorPage() {
const [prompt, setPrompt] = useState('');
const [imageUrl, setImageUrl] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setError(null);
setImageUrl(null);
setIsLoading(true);
// Call the Server Action
const result = await generateImageAction(prompt);
if (result.error) {
setError(result.error);
} else if (result.url) {
setImageUrl(result.url);
}
setIsLoading(false);
};
return (
<div>
<h2>Generative Image App</h2>
<form onSubmit={handleSubmit}>
<input
type="text"
value={prompt}
onChange={e => setPrompt(e.target.value)}
placeholder="Describe the image you want..."
disabled={isLoading}
style={{ width: '100%', padding: '0.5rem', marginBottom: '1rem' }}
/>
<button type="submit" disabled={isLoading}>
{isLoading ? 'Generating...' : 'Generate Image'}
</button>
</form>
{error && <p style={{ color: 'red' }}>Error: {error}</p>}
{imageUrl && (
<div style={{ marginTop: '1rem' }}>
<img src={imageUrl} alt={prompt} style={{ maxWidth: '100%' }} />
</div>
)}
</div>
);
}
다음 단계는?
- Streaming Tokens – 단순
await generateImage를 스트리밍 인터페이스로 교체하여 진행률 표시줄이나 중간 미리보기를 보여줍니다. - Persisted Storage – 최종 이미지를 Vercel Blob(또는 다른 객체 스토어)에 업로드하고 서명된 URL을 제공해 페이로드를 가볍게 유지합니다.
- Security – API 키는 반드시 서버에만 보관하고, 클라이언트 번들에는 절대 노출하지 않습니다.
- Advanced UI – React Server Components와 결합해 이미지가 스트리밍되는 동안 자리표시자를 사전 렌더링합니다.
TL;DR
이미지를 정적 파일이 아니라 실시간 연산으로 다루세요.
Vercel AI SDK를 사용해 프롬프트 → AI 생성 → 클라이언트 표시까지 안전하고 스트리밍되는 워크플로를 조정합니다.
명확한 상태 머신(Idle → Processing → Generating → Ready → Display)을 활용해 UI를 반응형이고 사용자 친화적으로 유지합니다.
Happy cooking! 🍳🚀
Server‑Sent Events (SSE)와 Vercel Blob을 사용한 이미지 생성
// Example component (simplified)
export default function ImageResult({ imageUrl }) {
return (
<div>
{imageUrl ? (
<img src={imageUrl} alt="Generated" />
) : (
<>Loading…</>
)}
</div>
);
}
왜 거대한 Base64 문자열을 반환하지 않을까?
실제 SaaS 애플리케이션에서는 Server Action에서 거대한 Base64 페이로드를 보내는 것이 위험합니다:
- 페이로드 제한 – 요청/응답 크기를 초과할 수 있습니다.
- 타임아웃 – 오래 걸리는 생성은 Vercel의 10 초 제한에 걸릴 수 있습니다.
- 사용자 경험 – 전체 이미지가 전송되는 동안 UI가 멈춥니다.
권장 아키텍처: “Marketing Asset Studio”
| Step | Description |
|---|---|
| 1️⃣ Client | Server Action을 호출하고 Vercel AI SDK의 useCompletion 또는 useChat을 사용해 스트림을 구독합니다. |
| 2️⃣ Server | 전체 이미지를 기다리지 않고 생성 시작합니다. 상태 업데이트를 스트리밍합니다(예: “Prompt received…”, “DALL‑E 3 processing…”, “Uploading…”). |
| 3️⃣ Server | OpenAI에서 바이트가 도착하는 즉시 Vercel Blob(또는 다른 오브젝트 스토어)에 업로드합니다. |
| 4️⃣ Server | 최종 Blob URL을 클라이언트에 스트리밍합니다. |
| 5️⃣ Client | URL을 받아 로딩 스켈레톤을 실제 이미지로 교체합니다. |
결과: UI가 절대 멈추지 않습니다. 사용자는 20초 정도 걸리는 백그라운드 생성 동안 진행 표시기(실제 또는 시뮬레이션)를 볼 수 있습니다.
일반적인 함정 및 해결책
| 문제 | 증상 | 해결책 |
|---|---|---|
| Vercel Timeout | 요청이 10 s 후에 408 오류로 실패합니다. | Serverless Function 타임아웃을 늘리세요 (Pro 플랜) 또는 작업을 백그라운드 큐(예: Vercel QStash)에 오프로드하세요. |
| Payload Too Large | Server Action이 조용히 실패하거나 오류를 발생시킵니다. | 절대 Base64를 반환하지 마세요. 액션 내부에서 이미지를 스토리지(Vercel Blob, S3 등)에 업로드하고 URL만 반환하세요. |
| Missing API Key | "Invalid API Key" 오류. | OPENAI_API_KEY가 .env.local에 정의되어 있는지 확인하세요. 절대 키를 소스 컨트롤에 커밋하지 마세요. |
| Async/Await Mismatch | 클라이언트가 [object Promise]를 받습니다. | Server Action은 **async**여야 하며, 클라이언트는 결과를 await해야 합니다(또는 SDK의 스트리밍 훅을 사용하세요). |
Source: …
개념 전환: 정적 자산에서 동적 합성으로
- 이전 모델: “식료품점” – 정적 파일을 필요할 때마다 가져옴.
- 새 모델: “레스토랑 주방” – 이미지를 실시간으로 생성하여 계산 파이프라인의 일부로 처리.
다음 기술을 활용:
- Vercel AI SDK (스트리밍 완성 & 채팅)
- React Server Components (보안 서버‑사이드 로직)
- Vercel Blob (객체 스토리지)
우리는 이미지를 사전에 존재하는 파일이 아니라 클라이언트가 구독할 수 있는 상태ful 출력으로 취급합니다.
핵심 요점
- 생성 UI = 프로세스에 구독하는 것이며, 단순히 데이터를 가져오는 것이 아닙니다.
- 프로토타입에서는 간단한 Base64 반환으로 충분합니다.
- 프로덕션 SaaS에서는 SSE 스트리밍 + Blob 스토리지를 사용해 UI를 반응형으로 유지하고 아키텍처를 확장 가능하게 합니다.
추가 읽을거리
- The Modern Stack: Building Generative UI with Next.js, Vercel AI SDK, and React Server Components – 포괄적인 로드맵.
- JavaScript 및 TypeScript 시리즈와 AI에 대한 Amazon 링크.