1. 스트리밍 데이터를 서버 컴포넌트에서 받아옵니다.
  2. 클라이언트 컴포넌트에서 ReadableStream 읽어들입니다.

3. 받아온 데이터를 점진적으로 마크다운으로 변환하여 화면에 표시합니다.

import { useEffect, useState } from "react";
import ReactMarkdown from "react-markdown";
import remarkGfm from "remark-gfm";

export default function StreamingMarkdown() {
  const [content, setContent] = useState("");

  useEffect(() => {
    const eventSource = new EventSource("/api/stream"); // 스트리밍 엔드포인트

    eventSource.onmessage = (event) => {
      setContent((prev) => prev + event.data + "\n"); // 새로운 데이터 추가
    };

    eventSource.onerror = () => {
      eventSource.close();
    };

    return () => {
      eventSource.close();
    };
  }, []);

  return (
    <div className="p-4 border rounded bg-white max-w-2xl mx-auto">
      <ReactMarkdown remarkPlugins={[remarkGfm]}>{content}</ReactMarkdown>
    </div>
  );
}

 

설명:

1. useState와 useEffect

useState를 사용해 수신한 데이터를 상태로 관리합니다.

useEffect에서 EventSource를 사용하여 /api/stream에서 오는 데이터를 스트리밍 방식으로 수신합니다.

2. eventSource.onmessage

서버에서 오는 데이터를 기존 내용에 추가하여 Markdown 형식의 문자열을 갱신합니다.

3. Markdown 렌더링

react-markdown 라이브러리를 사용하여 Markdown을 HTML로 변환합니다.

remarkGfm 플러그인을 추가해 테이블, 체크리스트 등을 지원합니다.

 

이제 /api/stream에서 Markdown 데이터를 스트리밍하면 자동으로 화면에 변환된 HTML이 표시됩니다. 🚀

 

 

'use client';

 

import ReactMarkdown from 'react-markdown';

import { useState, useEffect } from 'react';

 

interface MarkdownStreamProps {

  streamData: ReadableStream;

}

 

export default function MarkdownStream({ streamData }: MarkdownStreamProps) {

  const [content, setContent] = useState('');

 

  useEffect(() => {

    const readStream = async () => {

      const reader = streamData.getReader();

      const decoder = new TextDecoder();

 

      try {

        while (true) {

          const { done, value } = await reader.read();

          if (done) break;

          

          const text = decoder.decode(value);

          setContent(prevContent => prevContent + text);

        }

      } finally {

        reader.releaseLock();

      }

    };

 

    readStream();

  }, [streamData]);

 

  return (

    <div className="markdown-content">

      <ReactMarkdown>{content}</ReactMarkdown>

    </div>

  );

}

 

import MarkdownStream from '@/components/MarkdownStream';

 

async function getStreamingData() {

  // 예시: API 엔드포인트에서 스트리밍 데이터를 가져옴

  const response = await fetch('YOUR_API_ENDPOINT', {

    method: 'GET',

    headers: {

      'Accept': 'text/event-stream',

    },

  });

 

  return response.body;

}

 

export default async function Page() {

  const streamData = await getStreamingData();

 

  return (

    <main className="p-4">

      <MarkdownStream streamData={streamData} />

    </main>

  );

}

import { useEffect, useState } from "react";
import ReactMarkdown from "react-markdown";
import remarkGfm from "remark-gfm";

export default function StreamingMarkdown() {
  const [content, setContent] = useState("");

  useEffect(() => {
    const eventSource = new EventSource("/api/stream"); // 서버에서 SSE로 데이터 받기

    eventSource.onmessage = (event) => {
      setContent((prev) => prev + event.data + "\n"); // 스트리밍된 데이터 추가
    };

    eventSource.onerror = () => {
      eventSource.close();
    };

    return () => {
      eventSource.close();
    };
  }, []);

  return (
    <div className="p-4 border rounded bg-white max-w-2xl mx-auto">
      <ReactMarkdown remarkPlugins={[remarkGfm]}>{content}</ReactMarkdown>
    </div>
  );
}

'아이티 코드' 카테고리의 다른 글

로그아웃 토큰  (0) 2025.03.29
리액트플로우  (0) 2025.03.18
2. Dockerfile 작성 (Next.js 최적화)  (0) 2025.02.22
스트리밍 자료  (0) 2025.02.12
스트리밍  (0) 2025.02.11

+ Recent posts