--- 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 (
{children}
);
}
return (
{children}
);
},
pre: ({ node, ...props }) => (
),
// Headings — 작은 크기로 (챗 버블 안에 있으므로)
h1: ({ node, ...props }) => ,
h2: ({ node, ...props }) => ,
h3: ({ node, ...props }) => ,
// Lists
ul: ({ node, ...props }) => | ` 등에 global CSS 없이 스타일 주려면 컴포넌트 재정의 필요
- `@tailwindcss/typography` (`prose` 클래스) 대안도 있지만 **다크 테마 세부 조정에 한계** + 번들 ~30KB 추가
### ✅ `react-markdown` 10+ API 주의
- `code` 컴포넌트에 `inline` prop 이 타입에서 빠진 경우 있음 → `props as { inline?: boolean; ... }` 캐스팅
- props 에서 `node` destructure 후 `{...props}` 하면 `node` 가 DOM 으로 안 새나감
### ✅ Tailwind 이스케이프 이슈
- `even:bg-slate-950/40` 같은 조건부 스타일 적극 사용 — 테이블 가독성 크게 올림
- `overflow-x-auto` 래퍼는 **필수** — 긴 테이블이 모바일에서 넘치지 않게
### ⚠ 보안 — XSS
- `react-markdown` 은 **기본 HTML sanitize** (허용된 엘리먼트만 통과)
- 사용자가 직접 HTML 태그 넣어도 raw 로 렌더 안 함
- 단, `rehype-raw` 같은 HTML 플러그인 추가 시 `rehype-sanitize` 반드시 병행
### ⚠ 스트리밍 중 리렌더
- LLM 토큰 스트리밍 UI 의 경우 ` |
|---|