스트리밍 평문 중단: React Server Components 로 인터랙티브 UI 잠금 해제

발행: (2026년 2월 4일 오전 05:00 GMT+9)
11 min read
원문: Dev.to

I’m happy to translate the text for you, but I don’t see any content beyond the source line you provided. Could you please paste the text you’d like translated? Once I have the full passage, I’ll translate it into Korean while keeping the source link and formatting unchanged.

진화: 뉴스 티커에서 실시간 방송까지

옛 방식 (텍스트 전용)

실시간 뉴스 티커를 상상해 보세요. 정보가 지속적으로 흐르지만 정적입니다. 클라이언트는 수동적인 수신자입니다.
사용자가 “Q3 매출 차트를 보여줘” 라고 요청하면, AI는 차트를 설명하는 텍스트나 JSON 블롭을 스트리밍합니다. 사용자는 UI가 렌더링되기 전에 스트림이 끝나기를 기다려야 합니다.

새로운 방식 (스트리밍 UI)

이제, 기자가 피드에 인터랙티브 대시보드, 차트, 폼 등을 동적으로 삽입할 수 있는 실시간 방송을 상상해 보세요. 이것이 streamable‑ui 패턴입니다.
{"chart":"data…"} 를 보내는 대신, 서버는 직렬화된 React 컴포넌트를 전송합니다:

// Example placeholder – the actual component is streamed from the server

클라이언트는 이를 즉시 받아 하이드레이션하고, AI가 나머지 응답을 생성하는 동안에도 사용자는 마우스를 올리거나, 확대·축소하고, 클릭할 수 있습니다.

아키텍처: RSC와 서버 액션

이것이 내부적으로 어떻게 동작할까요? 두 가지 핵심 요소에 의존합니다:

  1. React Server Components (RSCs) – 서버가 컴포넌트 트리를 렌더링하고 이를 특수 페이로드(React의 Flight 프로토콜 사용)로 직렬화합니다. 이 페이로드는 SSE(Server‑Sent Events)를 통해 스트리밍됩니다.
  2. Server Actions – 스트리밍된 컴포넌트는 단순한 정적 HTML이 아닙니다. 버튼이나 폼 같은 인터랙티브 요소를 포함할 수 있으며, 이러한 요소는 Server Actions를 통해 서버의 보안 함수들을 호출합니다.

이로써 양방향 흐름이 만들어집니다:

방향설명
Server → ClientUI 컴포넌트를 스트리밍합니다.
Client → Server사용자가 해당 컴포넌트 안의 버튼을 클릭합니다.
Server → Client서버 액션이 실행되어, 추가 AI 생성이 일어나고 새로운 컴포넌트가 스트리밍될 수 있습니다.

“라이브 쇼핑 카트” 비유

라이브 스트리머가 제품을 판매하는 상황을 생각해 보세요.

ModeExperience
텍스트‑전용스트리머가 아이템을 설명합니다. 채팅에 질문을 입력합니다.
스트리밍 UI스트리머가 비디오 피드에 “Add to Cart” 버튼과 사이즈 선택기를 직접 오버레이합니다. 영상을 멈추지 않고 바로 지금 클릭합니다.

코드 예시: AI 대시보드 스트리밍

AI가 요약 보고서를 생성하고 이를 인터랙티브한 React 컴포넌트로 스트리밍하는 SaaS 기능을 만들어 보겠습니다.

1️⃣ 서버‑사이드 구현

File: app/api/generate-report/route.ts

// app/api/generate-report/route.ts
import { streamUI } from 'ai/rsc';
import { OpenAI } from 'openai';

const openai = new OpenAI({
  apiKey: process.env.OPENAI_API_KEY || '',
});

// The component to be streamed
const ReportComponent = ({ data }: { data: string }) => {
  return (
    <div>
      <h3>AI Generated Report</h3>
      <p>{data}</p>
      <button
        onClick={() => alert('Report acknowledged!')}
        className="mt-3 px-3 py-1 text-xs bg-blue-600 text-white rounded hover:bg-blue-700"
      >
        Acknowledge
      </button>
    </div>
  );
};

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

  const result = await streamUI({
    model: 'gpt-4-turbo-preview',
    system: 'You are a helpful assistant that generates concise reports.',
    prompt: `Generate a summary report for: ${prompt}`,

    // The Magic Mapping:
    // When the AI generates text, we wrap it in our React Component
    text: ({ content }) => {
      return <ReportComponent data={content} />;
    },

    initial: 'Generating report...',
  });

  return result.toAIStreamResponse();
}

2️⃣ 클라이언트‑사이드 구현

File: app/page.tsx

// app/page.tsx
'use client';

import { useCompletion } from 'ai/react';

export default function DashboardPage() {
  const {
    completion,
    input,
    handleInputChange,
    handleSubmit,
    isLoading,
  } = useCompletion({
    api: '/api/generate-report',
  });

  return (
    <div>
      <h2>SaaS Dashboard</h2>

      <form onSubmit={handleSubmit}>
        <input
          value={input}
          onChange={handleInputChange}
          placeholder="Enter prompt"
        />
        <button type="submit" disabled={isLoading}>
          Generate
        </button>
      </form>

      <h3>Output:</h3>
      {/* The SDK handles the deserialization of the RSC payload */}
      {completion ? (
        // The payload is already a React element, so we can render it directly.
        <>{completion}</>
      ) : (
        <p>No report generated yet.</p>
      )}
    </div>
  );
}

Note: completion returned by useCompletion is a React element (or a tree of elements) that the SDK reconstructs from the streamed RSC payload. No dangerouslySetInnerHTML is required. (※ 이 주석은 원문 그대로 유지했습니다.)

요약

  • Streaming UI는 서버가 AI가 아직 생성 중일 때 클라이언트에 인터랙티브한 React 컴포넌트를 푸시하도록 합니다.
  • React Server Components + Server Actions는 안전하고 양방향이며 실시간 UI 파이프라인을 제공합니다.
  • 이 패턴은 Vercel AI SDK(streamUI는 서버에서, useCompletion은 클라이언트에서)와 함께 작동하며, 모든 LLM 기반 워크플로에 적용할 수 있습니다.

이제 정적인 텍스트를 넘어 진정한 실시간 AI‑강화 경험을 만들 수 있습니다! 🚀

{isLoading && (
  <div>
    ● Streaming component...
  </div>
)}

전체 화면 제어

Enter fullscreen mode
Exit fullscreen mode

고급 패턴: LangGraph와 Max‑Iteration 정책

스트리밍 UI와 AI 에이전트를 결합하면 순환 워크플로우에 들어갑니다:

  1. AI가 UI를 생성합니다.
  2. 사용자가 UI와 상호작용합니다.
  3. 그 상호작용이 AI에 피드백되어 다음 단계를 생성합니다.

이것은 강력하지만 위험합니다. 가드레일이 없으면 AI가 구성 요소를 무한히 생성하는 루프에 빠질 수 있습니다.

해결책: Max‑Iteration 정책

LangGraph를 사용하면 AI 로직을 상태 저장 그래프로 구조화할 수 있습니다.
반복 횟수를 확인하는 조건부 엣지(“정책”)를 추가합니다. 횟수가 제한(예: 5단계)을 초과하면 그래프가 END 노드로 전환을 강제하여 프로세스를 정상적으로 종료합니다.

이는 AI 로직이 혼란스러워도 애플리케이션이 안정적으로 유지되도록 보장합니다.

피해야 할 일반적인 함정

  • Hallucinated JSON – LLM에게 React 컴포넌트 구조(JSON/JSX)를 생성하도록 요청하지 마세요. 실패합니다. 대신 content를 생성하도록 요청하고, 해당 콘텐츠를 서버에 미리 정의된 컴포넌트에 매핑하도록 하세요(코드 예시와 같이).
  • Vercel Timeouts – 서버리스 함수는 타임아웃(10 s–15 s)이 있습니다. AI 생성이 느리면 스트림이 중단될 수 있습니다. 항상 streamUI를 사용하세요(연결을 효율적으로 유지합니다) 그리고 프롬프트를 최적화하세요.
  • Hydration Errors – 서버 컴포넌트는 브라우저 API(window, document)에 접근할 수 없습니다. 클라이언트 측 인터랙티브가 필요하다면(예: 예제의 onClick), 이벤트 처리 로직이 클라이언트 하이드레이션 과정에서 처리되도록 하거나 Client Component로 감싸세요.

결론

스트리밍 React 컴포넌트는 우리를 **“Generative Text”**에서 “Generative UI.” 로 이동시킵니다.

이는 사용 경험을 수동적인 읽고‑기다리는 사이클에서 능동적이고 반복적인 협업으로 바꿉니다. Vercel AI SDK와 React Server Components를 활용하면 즉각적이고 깊이 있는 인터랙티브함을 느낄 수 있는 애플리케이션을 구축할 수 있습니다. AI가 단순히 무엇을 해야 하는지 알려주는 것이 아니라, 눈앞에서 바로 실행할 수 있는 도구를 만들어 줍니다.

여기서 시연된 개념과 코드는 책 The Modern Stack: Building Generative UI with Next.js, Vercel AI SDK, and React Server Components에 제시된 포괄적인 로드맵에서 직접 발췌한 것입니다 – Amazon LinkAI with JavaScript & TypeScript Series의 일부 (Amazon Link).

Leanpub에서 제공하는 다른 프로그래밍 전자책도 확인해 보세요.

Back to Blog

관련 글

더 보기 »

ReactJS ~React Server Components~

ReactJS ~React Server Components~의 커버 이미지 https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev...