React で Markdown を表示する際に便利な React-Markdown の使い方と、カスタムコンポーネントを適用する方法について解説します。
React-Markdown とは?
React-Markdown は、Markdown 形式のテキストを React コンポーネント内で簡単にレンダリングできるライブラリです。
通常の HTML に変換するだけでなく、カスタムコンポーネントを適用することで、特定の要素の見た目や動作を変更できます。
なんでこのライブラリを使ったの?
アサインしているプロジェクトではAIエージェントを使用したチャットボットを作成しています。
その返答を表示するための技術選定という前提で理由を上げていきます!
1. AI は構造化された文章を出力しやすい
Markdown(MD)形式は、見出し (#
)、リスト (-
)、コードブロック(```) などの記法が統一されているため、
AI が 一貫したフォーマットで文章を生成しやすい という特徴があります。
特に ChatBot のような応答型のシステムでは、可読性が高く、フォーマットが明確な出力 が求められるため、
Markdown を採用することで、回答の一貫性を維持しやすくなります。
2. AIエージェントとの相性
BedRock(AWS の大規模言語モデル)などのAIエージェントを利用したChatBotにMarkdownを採用することで、以下のメリットがあります。
- 整った文章で返しやすい → AIエージェントがフォーマットを維持しやすい
-
フロントエンドでのレンダリングが容易 →
ReactMarkdown
などのライブラリを使えば簡単に表示できる - 箇条書きやコードブロックで情報を整理しやすい → 手順説明や技術的な回答に適している
ChatBotの回答をそのままウェブやアプリで表示する場合、Markdownのまま処理すれば
ReactMarkdown
などのライブラリを活用してシンプルにレンダリングできるため、実装面でも合理的な選択肢となります。
使ってみた
準備
bun intall react-markdown
公式はnpm推してましたが、bunでもいけました⭕️
React-Markdown で Markdown を表示する
ReactMarkdownコンポーネントでは、通常の HTML のように <h1>
や <p>
を書くのではなく、Markdown 形式のテキストを ReactMarkdown
コンポーネントで直接レンダリングできます。
基本的な使い方は以下のとおりです。
import ReactMarkdown from 'react-markdown';
const text = `
# これは H1 タイトル
## これは H2 タイトル
- リスト項目 1
- リスト項目 2
`;
export default function App() {
return <ReactMarkdown>{text}</ReactMarkdown>;
}
#
は <h1>
に、##
は <h2>
に変換され、リスト (-
) も <ul><li>
に変換されます。
ReactMarkdown
を使うだけで、Markdown を HTML に変換できます。
React-Markdown のカスタムコンポーネント
デフォルトの HTML ではなく、見た目や動作をカスタマイズしたい場合は、components
を使って特定の要素をカスタムコンポーネントに置き換えることができます。
例えば、h1
のスタイルを変更する場合、以下のように h1
をカスタムコンポーネントに置き換えます。
カスタム h1 コンポーネントの作成
'use client';
import type { ClassAttributes, HTMLAttributes } from 'react';
import type { ExtraProps } from 'react-markdown';
export const ReactMarkDownCustomH1 = ({
children,
}: ClassAttributes<HTMLHeadingElement> & HTMLAttributes<HTMLHeadingElement> & ExtraProps) => {
return <h1 style={{ color: 'blue', fontSize: '1.5em' }}>{children}</h1>;
};
children
を受け取るだけで、Markdown の # タイトル
をカスタムできる。
また、他の HTML 属性(id
, className
など)も受け取れるように props
を適用している。
カスタムコンポーネントの適用
カスタム h1
コンポーネントを ReactMarkdown
に適用するには、components
プロパティを使います。
以下のように h1
を ReactMarkDownCustomH1
に置き換えます。
import ReactMarkdown from 'react-markdown';
import { ReactMarkDownCustomH1 } from './ReactMarkDownCustomH1';
const text = `
# これはカスタム H1 タイトル
## これは通常の H2 タイトル
`;
export default function App() {
return <ReactMarkdown components={{ h1: ReactMarkDownCustomH1 }}>{text}</ReactMarkdown>;
}
Markdown の # H1
を ReactMarkDownCustomH1
に置き換え、青色 & 1.5em のサイズにカスタマイズ。
## H2
はそのまま変換される。
画像 (img
) のカスタムコンポーネント
画像 (img
) もカスタマイズできます。
ReactMarkdown
では src
や alt
などの props
が自動で渡されるため、以下のように max-width
を調整した img
を作成できます。
const ReactMarkDownCustomImage = ({ src, alt }: { src?: string; alt?: string }) => {
return <img src={src} alt={alt || '画像'} style={{ maxWidth: '100%', borderRadius: '8px' }} />;
};
カスタム画像コンポーネントを適用するには、以下のように components
に img
を追加します。
<ReactMarkdown components={{ h1: ReactMarkDownCustomH1, img: ReactMarkDownCustomImage }}>
{text}
</ReactMarkdown>
画像の src
と alt
は ReactMarkdown
が自動で props
に渡してくれるので、特別な処理は不要。
また、max-width: 100%
を適用することで、画像の横幅を親要素に合わせることができる。
Markdown のテキストは children
で渡せばよい
React-Markdown のカスタムコンポーネントを作成する際、基本的に Markdown のテキストは children
として受け取れば問題ありません。
const ReactMarkDownCustomH1 = ({
children,
}: ClassAttributes<HTMLHeadingElement> & HTMLAttributes<HTMLHeadingElement> & ExtraProps) => {
return <h1 style={{ color: 'blue', fontSize: '1.5em' }}>{children}</h1>;
};
children
を受け取るだけで、Markdown の内容がそのまま渡される。
不要な props
を扱わなくても、テキスト情報は children
で取得可能。
例えば、次のように ReactMarkdown
に適用すれば、h1
と img
の両方をカスタマイズできます。
<ReactMarkdown components={{ h1: ReactMarkDownCustomH1, img: ReactMarkDownCustomImage }}>
{text}
</ReactMarkdown>
まとめ
React-Markdown を使うと、Markdown をそのまま HTML に変換できるだけでなく、
components
を利用して特定の要素の見た目や動作をカスタマイズできます。
基本的な使い方
<ReactMarkdown>{text}</ReactMarkdown>
このコードだけで Markdown を HTML に変換できる。
カスタムコンポーネントの適用
<ReactMarkdown components={{ h1: ReactMarkDownCustomH1, img: ReactMarkDownCustomImage }}>
{text}
</ReactMarkdown>
カスタム h1
や img
を適用し、デザインを調整できる。
React-Markdown を活用することで、Markdown のシンプルさを活かしつつ、柔軟にデザインを調整できるようになりました☺️