--- title: LLM 채팅 UI 마크다운 렌더링 (다크 테마 + Tailwind) tags: [frontend, react, llm, markdown, chat-ui, tailwind] created: 2026-04-23 --- # LLM 채팅 UI 마크다운 렌더링 ## 문제 LLM(Claude/GPT/Gemini) 이 생성하는 응답은 **거의 항상 마크다운 포함**: - 테이블 (요약/비교) - 목록, 체크박스 - 코드 블록 - 굵은 글씨, 링크 그런데 React 채팅 UI 에서 기본으로 `whitespace-pre-wrap` 만 쓰면 **원문 그대로 표시**: ``` **등록 크롤러 목록:** | ID | 이름 | 상태 | |----|------|------| | 1 | naver_place | ✅ 활성 | ``` 파이프 문자 그대로 보임 → 못생김 + 읽기 어려움. ## 해결 `react-markdown` + `remark-gfm` + Tailwind 커스텀 컴포넌트. ### 의존성 (npm) ```bash npm install react-markdown remark-gfm ``` - `react-markdown` (~50KB gzip): 마크다운 → React 엘리먼트 트리 - `remark-gfm` (~30KB gzip): GitHub Flavored Markdown — **테이블, 체크박스, 취소선 필수** ⚠️ 합계 약 150KB 번들 추가. SPA 채팅 UI엔 허용 수준. 일반 웹사이트에 부담되면 route-level code split. ### 컴포넌트 — `src/components/Markdown.tsx` ```tsx import ReactMarkdown from "react-markdown"; import remarkGfm from "remark-gfm"; /** * LLM 채팅 응답용 마크다운 렌더러. 다크 테마 + Tailwind 스타일 내장. * 지원: GFM 테이블, 체크박스, 취소선, 인라인/블록 코드, 목록, 링크, blockquote. */ export default function Markdown({ children }: { children: string }) { return (
(
), thead: ({ node, ...props }) => ( ), tr: ({ node, ...props }) => , th: ({ node, ...props }) => (
), td: ({ node, ...props }) => ( ), // Code — inline vs block 구분 (react-markdown 10+ 에서 inline prop 은 any 필요) code: (props) => { const { inline, className, children, ...rest } = props as { inline?: boolean; className?: string; children?: React.ReactNode }; if (inline) { return ( {children} ); } return ( {children} ); }, pre: ({ node, ...props }) => (
          ),

          // Headings — 작은 크기로 (챗 버블 안에 있으므로)
          h1: ({ node, ...props }) => 

, h2: ({ node, ...props }) =>

, h3: ({ node, ...props }) =>

, // Lists ul: ({ node, ...props }) =>