Markdown Renderer

Markdown Renderer 是前端将 LLM 输出的 Markdown 文本渲染为 React 组件的模块。它需要处理 GFM 语法、代码高亮、安全性和自定义组件。

#type / concept #status / evergreen #tech / frontend #tech / ai

[!info] related notes

Markdown Renderer

一句话定义

Markdown Renderer 是前端将 LLM 输出的 Markdown 文本渲染为 React 组件的模块。LLM 的输出天然包含 Markdown(标题、列表、代码块、表格),需要专门的渲染器来正确显示。

它解决什么问题

LLM 的输出是 Markdown 格式的文本。直接用 dangerouslySetInnerHTML 渲染有 XSS 风险。直接用 <pre> 显示又丢失了格式。需要一个安全、功能完整、可定制的渲染器。

核心原理

渲染管线

Markdown 字符串


remark 解析 → mdast (Markdown 语法树)


remark 插件处理 (remark-gfm: 表格、任务列表、删除线)


remark-rehype 转换 → hast (HTML 语法树)


rehype 插件处理 (rehype-highlight: 代码高亮)


React 组件映射 → React Elements

核心组件

import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
import { oneDark } from 'react-syntax-highlighter/dist/cjs/styles/prism';

function MarkdownRenderer({ content }: { content: string }) {
  return (
    <ReactMarkdown
      remarkPlugins={[remarkGfm]}
      components={{
        // 自定义代码块渲染
        code({ node, inline, className, children, ...props }) {
          const match = /language-(\w+)/.exec(className || '');
          return !inline && match ? (
            <SyntaxHighlighter
              style={oneDark}
              language={match[1]}
              PreTag="div"
            >
              {String(children).replace(/\n$/, '')}
            </SyntaxHighlighter>
          ) : (
            <code className={className} {...props}>
              {children}
            </code>
          );
        },
        // 自定义表格渲染
        table({ children }) {
          return <div className="table-wrapper"><table>{children}</table></div>;
        },
        // 自定义链接渲染
        a({ href, children }) {
          return <a href={href} target="_blank" rel="noopener noreferrer">{children}</a>;
        },
      }}
    >
      {content}
    </ReactMarkdown>
  );
}

remark-gfm 支持的语法

语法示例说明
表格| A | B |GFM 表格
任务列表- [x] done复选框
删除线~~strikethrough~~删除线
自动链接https://example.com自动识别 URL

典型工程实现

安全性

react-markdown 默认安全,不使用 dangerouslySetInnerHTML。但需要注意:

// URL 安全:默认只允许 http/https/mailto 等协议
// 阻止 javascript: 等危险协议
<ReactMarkdown
  urlTransform={(url) => {
    if (url.startsWith('javascript:')) return '';
    return url;
  }}
>
  {content}
</ReactMarkdown>

自定义组件

const customComponents = {
  // 代码块带复制按钮
  code({ node, inline, className, children, ...props }) {
    const [copied, setCopied] = useState(false);
    const text = String(children).replace(/\n$/, '');

    if (!inline) {
      return (
        <div className="code-block">
          <button onClick={() => {
            navigator.clipboard.writeText(text);
            setCopied(true);
          }}>
            {copied ? '✅' : '📋'}
          </button>
          <SyntaxHighlighter language="javascript" style={oneDark}>
            {text}
          </SyntaxHighlighter>
        </div>
      );
    }
    return <code>{children}</code>;
  },
};

常见坑

  1. 用 dangerouslySetInnerHTML: XSS 风险
  2. 不做代码高亮: 代码块没有语法高亮,可读性差
  3. 表格不滚动: 宽表格溢出容器
  4. 图片不限制大小: 大图片撑破布局
  5. 链接不加 target=“_blank”: 点击链接离开当前页面

参考资料

创建于 2026/6/30 更新于 2026/7/15