Markdown レンダリング
:LiTarget: 用途
Markdown を React で安全にレンダリング(XSS対策込み)。
:LiSparkle: 特徴
- XSS対策
- シンタックスハイライト
- カスタムレンダラ
:LiCode: コード(コピペ用)
'use client';
// 軽量 Markdown レンダラー(外部ライブラリ不要・XSS対策込み)
function escapeHtml(s: string) {
return s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
.replace(/"/g, '"').replace(/'/g, ''');
}
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: 注意事項
- 依存パッケージを忘れず追加