The Challenges of Markdown Rendering
In hr_blog2.0, blog posts are written in Markdown. The rendering system needs to support code highlighting, Mermaid diagrams, tables, and more.
Rendering Pipeline
graph TD
A[Markdown 텍스트] --> B[react-markdown]
B --> C[remark-gfm]
B --> D[remark-math]
C --> E[테이블, 체크박스]
D --> F[수학 수식]
B --> G[rehype-highlight]
G --> H[코드 하이라이팅]
B --> I[커스텀 렌더러]
I --> J[Mermaid 다이어그램]
I --> K[이미지 최적화]
The MarkdownRenderer Component
import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
import rehypeHighlight from 'rehype-highlight';
import mermaid from 'mermaid';
function MarkdownRenderer({ content }: { content: string }) {
return (
<ReactMarkdown
remarkPlugins={[remarkGfm]}
rehypePlugins={[rehypeHighlight]}
components={{
code({ className, children }) {
if (className === 'language-mermaid') {
return <MermaidDiagram chart={String(children)} />;
}
return <code className={className}>{children}</code>;
}
}}
>
{content}
</ReactMarkdown>
);
}
The Mermaid Component
function MermaidDiagram({ chart }: { chart: string }) {
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
if (ref.current) {
mermaid.initialize({ theme: 'dark' });
mermaid.render('mermaid-' + id, chart)
.then(({ svg }) => {
ref.current!.innerHTML = svg;
});
}
}, [chart]);
return <div ref={ref} className="mermaid-container" />;
}
Automatic Table of Contents (TOC) Generation
function extractHeadings(content: string) {
const headings: Heading[] = [];
const regex = /^(#{1,3})\s+(.+)$/gm;
let match;
while ((match = regex.exec(content)) !== null) {
headings.push({
level: match[1].length,
text: match[2],
id: slugify(match[2])
});
}
return headings;
}
Every post on this blog is rendered through this pipeline.