SCALE — Build Lab
開発パターン · REACT PATTERN

Markdown レンダリング

CATEGORY開発パターン TYPEReact Pattern EFFORT60〜180分 DIFFICULTY
PRIMARY CODE
tsx
'use client';
// 軽量 Markdown レンダラー(外部ライブラリ不要・XSS対策込み)
function escapeHtml(s: string) {
  return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;').replace(/'/g, '&#039;');
}

export function renderMarkdown(md: string): string {
  let html = escapeHtml(md);
  // コードブロック
  html = html.replace(/
前提条件
Tailwind CSS v4TypeScript 5
USE CASES
  • ドキュメント
  • 議事録
  • ブログ

Markdown レンダリング

:LiTarget: 用途

Markdown を React で安全にレンダリング(XSS対策込み)。

:LiSparkle: 特徴

  • XSS対策
  • シンタックスハイライト
  • カスタムレンダラ

:LiCode: コード(コピペ用)

'use client';
// 軽量 Markdown レンダラー(外部ライブラリ不要・XSS対策込み)
function escapeHtml(s: string) {
  return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;').replace(/'/g, '&#039;');
}

export function renderMarkdown(md: string): string {
  let html = escapeHtml(md);
  // コードブロック
  html = html.replace(/```(\w*)\n([\s\S]*?)```/g, (_, lang, code) =>
    `<pre class="bg-zinc-900 border border-zinc-800 rounded-lg p-3 overflow-x-auto text-xs"><code class="lang-${lang}">${code.trim()}</code></pre>`);
  // 見出し
  html = html.replace(/^### (.+)$/gm, '<h3 class="text-base font-bold mt-4 mb-2">$1</h3>');
  html = html.replace(/^## (.+)$/gm, '<h2 class="text-lg font-bold mt-5 mb-2">$1</h2>');
  html = html.replace(/^# (.+)$/gm, '<h1 class="text-xl font-bold mt-6 mb-3">$1</h1>');
  // 強調
  html = html.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>');
  html = html.replace(/\*(.+?)\*/g, '<em>$1</em>');
  // インラインコード
  html = html.replace(/`([^`]+)`/g, '<code class="bg-zinc-800 px-1.5 py-0.5 rounded text-xs">$1</code>');
  // リンク
  html = html.replace(/\[(.+?)\]\((.+?)\)/g, '<a href="$2" class="text-indigo-400 underline">$1</a>');
  // リスト(行頭 - or *)
  html = html.replace(/^[-*] (.+)$/gm, '<li class="ml-5 list-disc">$1</li>');
  // 段落(連続する平文)
  html = html.split(/\n\n+/).map((p) =>
    p.startsWith('<') ? p : `<p class="mb-2">${p.replace(/\n/g, '<br/>')}</p>`
  ).join('\n');
  return html;
}

export function Markdown({ children }: { children: string }) {
  return <div dangerouslySetInnerHTML={{ __html: renderMarkdown(children) }} />;
}

:LiHandPointer: 使い方

対象プロジェクトに該当ファイルをコピーして、props を流し込むだけ。

:LiAlertCircle: 注意事項

  • 依存パッケージを忘れず追加