마크다운은 개발 문서, 블로그, 릴리즈 노트 등을 빠르게 작성하기에 최적의 포맷입니다. React 앱에서 마크다운을 안전하고 보기 좋게 렌더링하는 방법을 실무 중심으로 정리합니다.
1. 어떤 라이브러리를 쓸까요?
react-markdown은 마크다운을 React 컴포넌트로 변환해주는 표준 선택지입니다. GitHub-Flavored Markdown은 remark-gfm으로 지원하며, 제목 앵커, 자동 링크, 보안은 rehype 플러그인들로 강화합니다. 코드 하이라이트는 react-syntax-highlighter 또는 rehype-highlight를 사용할 수 있습니다.
2. 설치
// 필수 라이브러리 설치
// npm i react-markdown remark-gfm rehype-slug rehype-autolink-headings
// 코드 하이라이트용 선택
// npm i react-syntax-highlighter
// 보안 강화를 원하면
// npm i rehype-sanitize3. 최소 구현: 기본 렌더러
import React from 'react';
import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
import rehypeSlug from 'rehype-slug';
import rehypeAutolinkHeadings from 'rehype-autolink-headings';
export function MarkdownRenderer({ markdown }) {
return (
<ReactMarkdown
remarkPlugins={[remarkGfm]}
rehypePlugins={[rehypeSlug, [rehypeAutolinkHeadings, { behavior: 'wrap' }]]}
>
{markdown}
</ReactMarkdown>
);
}위 구현은 GFM 표와 체크박스, 제목에 앵커 링크를 제공합니다. raw HTML을 허용하지 않으므로 기본적으로 안전합니다.
4. 코드 블록 하이라이트 추가
import React, { useMemo } from 'react';
import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
import rehypeSlug from 'rehype-slug';
import rehypeAutolinkHeadings from 'rehype-autolink-headings';
import { Light as SyntaxHighlighter } from 'react-syntax-highlighter';
import js from 'react-syntax-highlighter/dist/esm/languages/hljs/javascript';
import ts from 'react-syntax-highlighter/dist/esm/languages/hljs/typescript';
import jsonLang from 'react-syntax-highlighter/dist/esm/languages/hljs/json';
import oneDark from 'react-syntax-highlighter/dist/esm/styles/hljs/atom-one-dark';
SyntaxHighlighter.registerLanguage('js', js);
SyntaxHighlighter.registerLanguage('javascript', js);
SyntaxHighlighter.registerLanguage('ts', ts);
SyntaxHighlighter.registerLanguage('json', jsonLang);
export function MarkdownRenderer({ markdown }) {
const md = useMemo(() => markdown || '', [markdown]);
return (
<ReactMarkdown
remarkPlugins={[remarkGfm]}
rehypePlugins={[rehypeSlug, [rehypeAutolinkHeadings, { behavior: 'wrap' }]]}
components={{
code({ inline, className, children, ...props }) {
const match = /language-(\w+)/.exec(className || '');
const lang = match?.[1] || 'plaintext';
if (!inline) {
return (
<SyntaxHighlighter language={lang} style={oneDark} PreTag="div" {...props}>
{String(children).replace(/\n$/, '')}
</SyntaxHighlighter>
);
}
return <code className={className} {...props}>{children}</code>;
},
}}
>
{md}
</ReactMarkdown>
);
}마크다운의 ```js 처럼 언어 힌트를 사용하면 자동으로 하이라이트가 적용됩니다.
5. 링크와 이미지 UX 개선
// 외부 링크는 새 창, 이미지 지연 로딩
components={{
a({ href, children, ...props }) {
const isExternal = href && /^https?:\/\//.test(href);
return (
<a
href={href}
target={isExternal ? '_blank' : undefined}
rel={isExternal ? 'noopener noreferrer' : undefined}
{...props}
>{children}</a>
);
},
img({ src, alt, ...props }) {
return <img src={src || ''} alt={alt || ''} loading="lazy" {...props} />;
}
}}사용자 경험과 SEO를 함께 고려해 외부 링크 보안 속성을 추가하고 이미지 지연 로딩을 적용합니다.
6. 보안과 XSS 대응
react-markdown은 기본적으로 raw HTML을 렌더링하지 않아 안전합니다. 필요 시 HTML을 허용하려면 rehype-raw를 쓰게 되는데, 이 경우 반드시 rehype-sanitize로 허용 목록 기반 정화를 적용해야 합니다.
// HTML을 일부 허용해야 하는 경우: sanitize 스키마 확장 예시
import rehypeRaw from 'rehype-raw';
import rehypeSanitize, { defaultSchema } from 'rehype-sanitize';
const schema = {
...defaultSchema,
attributes: {
...defaultSchema.attributes,
code: [...(defaultSchema.attributes.code || []), ['className', 'language-*']],
span: [...(defaultSchema.attributes.span || []), ['className', 'token *']],
},
};
<ReactMarkdown
remarkPlugins={[remarkGfm]}
rehypePlugins={[
rehypeSlug,
[rehypeAutolinkHeadings, { behavior: 'wrap' }],
rehypeRaw,
[rehypeSanitize, schema],
]}
>{markdown}</ReactMarkdown>가능하면 rehype-raw 없이 운영하는 것이 가장 안전합니다. 외부 입력을 렌더링할 때는 항상 허용 목록 접근을 유지합니다.
7. 마크다운 소스 불러오기
import React, { useEffect, useState } from 'react';
import { MarkdownRenderer } from './MarkdownRenderer';
export function Article() {
const [md, setMd] = useState('');
useEffect(() => {
fetch('/docs/readme.md')
.then((r) => r.text())
.then(setMd)
.catch(() => setMd('# 로드 실패'));
}, []);
return <MarkdownRenderer markdown={md} />;
}정적 파일, CMS, GitHub API 등 다양한 소스에서 텍스트를 불러와 렌더링할 수 있습니다.
8. SEO와 SSR 고려
검색엔진 크롤러가 콘텐츠를 인덱싱하려면 SSR이 유리합니다. Next.js에서 react-markdown은 서버 컴포넌트에서도 동작하지만, 코드 하이라이트처럼 브라우저 의존성이 있는 기능은 클라이언트 컴포넌트로 분리하거나 동적 로딩을 권장합니다.
// Next.js에서 하이라이트만 클라이언트로 분리 예시 (app router)
'use client';
import dynamic from 'next/dynamic';
const SyntaxHighlighter = dynamic(
() => import('react-syntax-highlighter').then((m) => m.Light),
{ ssr: false }
);
// 나머지 react-markdown은 서버에서 렌더링하여 SEO를 확보합니다.제목 앵커, 메타 태그, 구조화 데이터 등과 함께 서버 렌더링을 적용하면 블로그 SEO에 효과적입니다.
9. 성능 최적화 팁
큰 문서를 렌더링할 때는 하이라이트 라이브러리와 스타일을 필요한 언어만 등록하고, React.lazy 또는 dynamic import로 지연 로딩합니다. 마크다운 문자열이 자주 변하지 않으면 useMemo로 재연산을 줄이고, 매우 긴 목록은 가상화 라이브러리를 고려합니다.
10. 실전 체크리스트
외부 입력인지 여부를 결정하고 보안 정책을 설정합니다. 코드 블록 언어 힌트를 표준화합니다. 링크와 이미지에 UX와 보안 속성을 적용합니다. SSR 환경에서 동작을 검증합니다. 큰 문서 성능과 번들 크기를 모니터링합니다.
11. 샘플 데이터로 빠르게 검증
const sample = `# 마크다운 렌더링 테스트\n\n- **굵게**와 _기울임_\n- 코드:\n\n\`inline code\`\n\n\n\`\`\`js\nconsole.log('하이라이트');\n\`\`\`\n`;
// 렌더링
<MarkdownRenderer markdown={sample} />위 샘플로 GFM, 제목 앵커, 코드 하이라이트가 기대대로 동작하는지 즉시 확인합니다.
12. 마무리
react-markdown을 기반으로 GFM, 앵커, 하이라이트, 보안까지 구성하면 실무에서도 바로 활용 가능한 마크다운 렌더링 환경을 갖출 수 있습니다. SSR을 병행하고 성능을 다듬으면 문서형 콘텐츠의 SEO와 사용자 경험이 모두 개선됩니다.
'React' 카테고리의 다른 글
| React에서 키보드 단축키 시스템 구축하기 (0) | 2026.05.22 |
|---|---|
| React로 차트 컴포넌트 직접 설계 및 최적화하기 (0) | 2026.05.22 |
| React에서 지도 API(Google Maps, Leaflet) 통합하기 (0) | 2026.05.21 |
| React 앱에 GraphQL 캐싱 전략 도입하기 (0) | 2026.05.20 |
| React에서 WebGL 직접 구현하여 인터랙티브 그래픽 만들기 (0) | 2026.05.20 |