- 스트리밍 데이터를 서버 컴포넌트에서 받아옵니다.
- 클라이언트 컴포넌트에서 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>
);
}