MDXとは、Markdownで記述された文章中に直接JSX(Reactのコンポーネント)を埋め込めるようにする仕組みです。通常、Markdownは静的なHTMLに変換されるため動的な振る舞いを持ちませんが、MDXを使うことで文章の中にインタラクティブなUI要素を組み込むことができます。「コンポーネント時代のMarkdown」とも呼ばれるように、Markdownの手軽さとReactコンポーネントのパワフルさを両取りできるのが特徴です。例えば記事の中にグラフやアラートのコンポーネントを埋め込んだり、フォームやボタンを配置してユーザーと対話できるコンテンツを作成することも可能です。これにより、今までMarkdownでは難しかったリッチで動的な表現も、無理に純粋なHTMLを書くことなく実現できます。
本記事では、MDXの概要やメリット、基本的な構文と使い方から、開発環境への導入方法、コンポーネント活用の例、さらには実際の活用シーンや注意点まで解説します。MarkdownとReactの基礎知識があれば読み進められる内容になっています。
MDXとは?
MDXは簡単に言えば「Markdown + JSX」です。Markdown記法で書いたテキストと、JSXで書いたReactコンポーネントを同じファイル内で混在させることができます。MDXファイル(拡張子.mdx)は最終的にReactコンポーネントとして解釈されるため、文章とUIを一体化して管理・再利用できます。
MDXの主な特徴とメリットをまとめると次のとおりです:
- MarkdownとJSXのシームレスな統合: 見出しやリストなどは普段通りMarkdownで簡潔に書きつつ、必要な箇所でJSX記法を挿入してリッチなUIを組み込めます。
- すべてがコンポーネント: 埋め込まれたUI要素だけでなく、Markdownで書かれた文章自体もReactコンポーネントとして扱えるため、他のページからインポートして再利用したりネストしたりできます。
- コンポーネントの埋め込み: 任意のReactコンポーネントをインポートしてMarkdown中に直接配置できます。たとえばチャートやボタンなどのコンポーネントを記事内に自由に埋め込めます。
- レンダリングのカスタマイズ性: 各Markdown要素(見出しやリスト項目など)に対して、デフォルトのHTMLタグの代わりに好きなReactコンポーネントを対応させることが可能です(例:
<h1>をカスタム見出しコンポーネントに差し替える)。 - Markdownのシンプルさを維持: 基本はMarkdown主体で書けるので文章の可読性・記述性はそのままに、必要な部分だけをJSXで拡張できます。
- ビルド時コンパイルによる高速動作: MDX自体に実行時のオーバーヘッドはなく、ビルド時に通常のJavaScriptコード(Reactコンポーネント)へと変換されます。そのため表示時のパフォーマンスは良好です。
以上のように、MDXはMarkdownの持つ手軽さ・読みやすさと、Reactコンポーネントの再利用性・動的機能を両立したフォーマットです。特に技術ドキュメントやブログ記事など、「文章の中にちょっとしたインタラクティブ要素を入れたい」といった場面で威力を発揮します。
MDXの基本構文
MDXファイルではMarkdown記法とJSX記法を自由に混在させることができます。見出しやリスト、表といった一般的なMarkdown構文はそのまま使え、加えてJSXのタグやJavaScriptの式も記述できます。さらにESモジュールのimport/export文も利用可能で、他のコンポーネントや値をインポートしたり、そのMDX内で変数・関数をエクスポートして再利用することもできます。
例えば、次のようなMDXコードを見てみましょう
import { SalesChart } from './components/SalesChart'
export const year = 2025
# {year}年 売上レポート
{year}年は前年比で売上が大きく成長しました。
特に第3四半期には新商品がヒットし、全体の収益を押し上げました。
<SalesChart year={year} color="#ccddee" />
上記は簡単なMDXファイルの例です。import文で外部からSalesChartコンポーネントを読み込み、export const yearで変数yearを定義しています。その下にある文章はMarkdownの見出し(<h1>要素)として解釈され、その下の文章は段落(<p>要素)になります。文章中の {year} はJavaScriptの式で、先ほど定義したyearの値(ここでは2023)が展開されます。最後の <SalesChart year={year} color="#ccddee" /> はJSX記法で差し込まれたReactコンポーネントであり、指定したyearやcolorのプロパティを渡してレンダリングされます。
このように、Markdownの記法とJSX記法を一つのファイルで併用できるのがMDXの基本です。結果として生成されるMDXファイルは通常のReactコンポーネントでもあります。 つまり上記の例では、このMDXファイル自体を他のReactコンポーネントからインポートして<Article />(例えばArticleという名前でインポートした場合)といった形でレンダリングできます。文章とコンポーネントが同居するMDXコンテンツは、そのままアプリケーション内で再利用可能な部品になるわけです。
なお、MDXは標準ではCommonMark準拠のMarkdown構文に対応しており、通常のMarkdownで使える見出しやリスト、コードブロック、画像埋め込みなどは一通りサポートされています。例えば上記の例でも段落内に改行や強調(**...**)などMarkdownの書式と、JSXのカスタムコンポーネント呼び出しが混在しています。Markdownにない拡張機能(テーブルやチェックボックスリスト、数式記法など)も、必要に応じてMDXに対してプラグインを導入することで利用できます。基本構文を押さえたところで、次にこのMDXを実際のプロジェクトで使う方法を見てみましょう。
開発環境での使い方
MDXをプロジェクトで利用するには、ビルドプロセスにMDXのコンパイルを組み込む必要があります。MDXファイルはそのままブラウザで解釈できるわけではなく、事前にReactコンポーネント(JavaScript)に変換しておく工程が必要だからです。しかし心配はいりません。MDX公式のライブラリから、主要なビルド環境向けに便利なパッケージが提供されています 。
例えば以下のような公式パッケージがあります:
- webpackやNext.jsの場合:
@mdx-js/loader(ローダーとしてMDXを処理) - RollupやViteの場合:
@mdx-js/rollup(プラグインとしてMDXを処理) - esbuildやBunの場合:
@mdx-js/esbuild(プラグインまたはビルトインでMDXを処理) - Bundlerを使わない場合: Node.js環境で
@mdx-js/node-loaderを利用して.mdxファイルをインポートしたり、もしくは@mdx-js/mdxというコアコンパイラを直接呼び出してMDX文字列をコンポーネントにコンパイルすることもできます。
既存のReactフレームワークや静的サイトジェネレータでは、MDX対応が比較的簡単に導入できるようになっています。例えばNext.jsには公式の@next/mdxプラグインがあり、数行の設定で.mdx拡張子のページを扱えるようになります。Gatsbyでもgatsby-plugin-mdxが公式提供されており、従来のMarkdownファイルを置き換える形でMDXを利用できます。またDocusaurusやAstroといったMarkdown主体のフレームワークは初めからMDXとの相性を考えて作られており、プラグイン導入やデフォルト機能としてMDXをサポートしています。このように、多くの環境でMDXは標準もしくはオプションとして統合されています。
具体的な導入方法は環境によって様々ですが、概ね以下のような手順になります。
- 依存パッケージのインストール: 上記の公式パッケージ(例: webpackなら
@mdx-js/loaderと@mdx-js/react、Rollupなら@mdx-js/rollupなど)をプロジェクトに追加します。Reactで利用する場合は後述するMDX用のReactコンポーネント集@mdx-js/reactもインストールすると良いでしょう。 - ビルド設定の追加: bundlerの設定ファイルにMDXファイルの処理ルールを追加します。例えばwebpackなら
.mdx拡張子に対して先述のMDXローダーを適用する設定を追記します。Next.jsの場合はnext.config.jsでページ拡張子にmdxを含め、プラグインを読み込む設定を行います。 - MDXファイルの作成とインポート: 設定が完了すると、プロジェクト内で
.mdxファイルを作成して通常のReactコンポーネントと同様にインポートできるようになります。例えばPost.mdxというファイルを書いたら、import Post from "./Post.mdx";として読み込み、<Post />とJSX内で利用できます。
💡 補足: MDX公式のパッケージ群はモダンなJavaScriptで書かれており、Node.jsはバージョン16以上(ESM対応)が必要です。古い環境では動作しないので注意してください。
以上のように設定すれば、あたかもMarkdownファイルがそのままReactコンポーネントになったかのように開発できるようになります。特にNext.jsやGatsbyなどでは公式ドキュメントやテンプレートが充実しているので、初めてでも比較的簡単にMDX環境を構築できるでしょう。次は、MDXで実際にコンポーネントを活用する方法を具体的に見てみます。
コンポーネントの活用例
MDX最大の魅力は、やはり好きなReactコンポーネントを文章中に埋め込めることです。前述の例では<Chart>コンポーネントを埋め込みましたが、実際にはあらゆるコンポーネントを配置できます。ここではMDXでコンポーネントを活用するいくつかの方法を紹介します。
まず、MDXファイル内でコンポーネントを使う基本は「そのコンポーネントをスコープ内に用意する」ことです。具体的には以下の2通りがあります。
- 各MDXファイル内でインポートする方法: 必要なコンポーネントをそのMDXファイルの先頭で
importし、以降でJSX記法として使用します。多くの場合この方法で十分です(例:import Button from "../components/Button.js"として<Button variant="success">Click</Button>を記述)。 - MDXProviderで一括指定する方法: 複数のMDXファイルに共通のコンポーネントを毎回インポートするのが手間な場合や、Markdownのデフォルト要素を置き換えたい場合には、
MDXProviderコンポーネントを使ってアプリケーション側でコンポーネントを提供できます。
MDXProviderはMDXとReactをつなぐコンテキストプロバイダで、MDX内でレンダリングされる要素を好きなReactコンポーネントに置き換えることができます。デフォルトでは、MDX中のMarkdown記法はすべて通常のHTML要素(例えば# 見出しは<h1>、![画像]は<img>タグ)に変換されますが、MDXProviderを使うとそれらを自前のコンポーネントでオーバーライドできます。また、後述するように毎回MDXファイル側でインポートしなくてもグローバルにコンポーネントを注入する用途にも利用できます。
以下はMDXProviderの利用例です。たとえばMarkdownの見出し(<h1>タグ)をカスタムコンポーネントに差し替える場合、アプリケーションのルートで次のように設定します。
import { MDXProvider } from "@mdx-js/react";
import Post from "./Post.mdx";
// カスタムコンポーネントのマッピング定義
const components = {
h1: (props) => <h1 style={{ color: "tomato" }} {...props} />
};
function App() {
return (
<MDXProvider components={components}>
<Post />
</MDXProvider>
);
}
上記では、MDXProviderでcomponentsというマッピングオブジェクトを渡しています。キーにタグ名(またはMDX上のコンポーネント名)、値に対応させたいReactコンポーネントを指定します。例ではh1キーに対して、新たにスタイルを付与した<h1>要素を返す無名関数コンポーネントを割り当てました。この結果、Post.mdx内に登場するすべてのH1見出し(Markdownで書かれた# 見出し)はデフォルトの<h1>ではなく、このカスタムコンポーネントでレンダリングされます。他の要素(段落やリストなど)は指定しなければ引き続きデフォルトのHTMLタグでレンダリングされます。
同様に、例えばリンク用の<a>タグをNext.jsの<Link>コンポーネントに置き換えたり、画像<img>タグをカスタム画像コンポーネントに差し替えることも可能です。componentsオブジェクトにプロパティを追加していくだけで、Markdown由来のあらゆる要素を自分好みのReactコンポーネントで描画できます。
💡 豆知識: MDXProviderをネストすることもでき、ネストした場合はコンポーネントマッピングがマージされます。全体で共通のマッピングを指定しつつ、一部の画面で追加のコンポーネント置換を行う、といった柔軟な使い方も可能です。
さらにMDXProviderは、グローバルなショートコード提供として使うこともできます。例えば多くのMDXファイルで<Chart>コンポーネントを使いたい場合、毎回各MDX内でimport { Chart } from "...";を書く代わりに、アプリ側でMDXProviderのcomponentsにあらかじめChartを登録しておくことができます。こうしておけば各MDXファイルではインポート無しに <Chart /> と書くだけでそのコンポーネントが使えるようになります(※この場合、MDXコンパイラは未定義のコンポーネントを検知して内部的にContext経由で参照しにいく仕組みになっています)。
以上のように、MDXでは個別コンポーネントの埋め込みから全体への適用まで、様々な形でReactコンポーネントを活用できます。文章の内容に応じてコンポーネントを使い分けたり、デザインシステムに沿った表示に差し替えたりと、柔軟な表現力を得られるのがMDXの強みです。
実際の活用シーン
では、MDXは具体的にどのような場面で使われているのでしょうか。ここではドキュメント、ブログ、プレゼンテーションという3つの代表的な活用シーンを紹介します。
- ドキュメントサイト: ソフトウェアのドキュメンテーションや技術資料のサイトでMDXは大活躍します。例えばFacebook製のドキュメントフレームワークDocusaurusではMDXを標準採用しており、文章中にタブ切り替えコンポーネントを埋め込んでOSごとのコードスニペットを表示するといったリッチな機能を実現しています。Markdownベースのドキュメントにインタラクティブな要素(注意用のアラートボックス、コードサンドボックス埋め込みなど)を追加できる点で、MDXはドキュメント用途に最適です。
- 技術ブログ: 開発者のブログ記事や技術解説記事でもMDXの利用が増えています。文章の合間にReactで作成したグラフや動的なデモコンポーネントを挿入したり、スタイル化された注意書きや警告ボックスをコンポーネントで差し込んだりできます。実際、MDXはブログやドキュメント、そしてSEOにも優れたインタラクティブコンテンツにうってつけのフォーマットだと言われています。静的なMarkdownでは難しかった「読者が操作できる記事コンテンツ」も、MDXなら一つのMarkdownファイル内で完結に実装できるのです。
- プレゼンテーション: MDXはスライド資料の作成にも応用されています。代表的なものにmdx-deckというオープンソースツールがあり、MDXファイルにスライドごとに区切り記号(
---)を入れることでプレゼン資料を作成できます。mdx-deckでは「Markdownで手軽にスライドを書き、必要に応じてReactコンポーネントをインポートしてインタラクティブなデモを組み込める」というコンセプトで、実際にコードスニペットの実行プレビューやアニメーションコンポーネントをスライド内に埋め込むことも可能です。このように、MDXを使えばプレゼン資料も単なる静的PDFではなくリッチで動的なWebコンテンツとして提供できます。
上記のほかにも、Storybook(UIコンポーネントのドキュメント作成ツール)がドキュメンテーションにMDX形式を採用したり、Next.jsやGatsbyを使った企業サイトのブログエンジンにMDXが組み込まれたりと、MDXは「文章+α」のユースケースで幅広く利用されています。静的コンテンツと動的UI要素の架け橋として、年々その採用シーンは拡大しています。
注意点と落とし穴
便利なMDXですが、導入・利用にあたって留意すべきポイントもいくつかあります。最後によくある注意点や落とし穴をまとめます。
- サーバーサイドレンダリング(SSR)への対応: MDXコンテンツは事前にReactコンポーネントにコンパイルされていれば、通常のReactコンポーネント同様にSSRが可能です。実際、Next.jsのMDXプラグインではサーバーコンポーネントとしてMDXをレンダリングすることに対応しています。ただし、MDX内で使用するコンポーネントがブラウザにのみ存在するAPI(例えば
windowやdocument)に依存しているとSSR時にエラーになる可能性があります。そのため、SSR環境でMDXを使う場合はビルド時にコンパイルを行うこと、およびクライアント専用の処理は適切に分離することが重要です。Next.jsやGatsbyのようなフレームワークを利用すれば、基本的なSSR対応は自動で処理されますが、自前でMDXをサーバー上で扱う場合はNode.jsのESM対応(Node 16+)が必要になる点も注意してくだい。 -
TypeScriptの型定義:
.mdxファイルをTypeScriptでインポートすると型エラーになることがあります。これはTypeScriptがデフォルトではMDXモジュールを型定義できないためです。その対策として@types/mdxパッケージをインストールする方法があります。npm install --save-dev @types/mdxで型定義を追加すれば、MDXファイルをインポートしたときにそのコンポーネント型(React.FC相当)が認識されるようになります。例えば以下のようにインポートして使った場合も型エラーが解消され、Propsの型チェックも有効になります。import Post from "./Post.mdx"; // PostはReactコンポーネント(Functional Component)型として扱われる <Post title="Hello" />上記のように、適切に型設定すればTypeScript環境でもMDXコンテンツを安全に利用できます。
- 構文上の注意(エスケープと改行): MDXはMarkdownとJSXが混ざっている分、純粋なMarkdownに比べていくつか文法上の制約があります。例えば文章中に
<や{といった文字を書くとJSXや式とみなされてしまうため、そのまま表示したい場合は\<や{'{'}{'}'}のようにエスケープが必要です。また、JSXタグの前後には空行が必要な場合があります。例えばMDX中に JSXのブロックを挿入する際、その前後を空行で区切らないと正しくパースされないことがあります。さらに、Markdown中にHTMLコメントを書くことはできないため(JSXのコメント構文{/* ... */}を使う必要があります)、これも注意が必要です。純粋なMarkdownに比べると「文法上気を遣う点が増える」という印象は否めません。 - 可読性とメンテナンス: MDXは強力ですが、何でもかんでもコンポーネントを埋め込めるからといって乱用すると文章としての可読性が下がる恐れがあります。文章量に比してJSXの占める割合が多くなると、コンテンツ制作者(技術ライター等)にとっては内容を追いにくくなるかもしれません。チームでMDXコンテンツを管理する場合、どこまでをMarkdownで書き、どこからをコンポーネントにするか指針を決めておくと良いでしょう。一般に、簡単な装飾やレイアウト調整程度であればMarkdown+MDXで対応し、それ以上に複雑なUIロジックが必要な場合は無理にMDX内に書かず純粋なReactコンポーネントとして実装する方が見通しが良くなることもあります。「コンテンツ」と「アプリケーションコード」のバランスを取りながらMDXを活用することが大切です。
- 他環境へのロックイン: 最後に、MDXはその性質上特定の技術スタックへの依存が強い点も念頭に置いておきましょう。Markdownが汎用フォーマットで様々なツールで扱えるのに対し、MDXは基本的にReactなどJSXを解釈できる環境でしか利用できます。将来的に他の静的サイトジェネレータや異なるフレームワークへ移行したいとなった場合、MDXで書かれた記事を直接再利用することはできず、何らかの変換が必要になります。MDXそのものも成熟してきてはいますが、エコシステムの規模はMarkdownに比べれば小さいため、このロックインのリスクについても理解した上で採用を判断するとよいでしょう。
まとめ
MDXはMarkdownとReactコンポーネントの世界を融合させる画期的な手法であり、初心者にとっても比較的理解しやすく、使い始めやすいツールです。普段書き慣れたMarkdownに少し手を加えるだけで、動的なコンテンツや美麗なUIコンポーネントを文章中に組み込めるのは大きなメリットと言えます。実際、ドキュメントサイトからブログ、プレゼン資料まで幅広い分野でMDXの活用が進んでおり、静的な文章にインタラクティブ性を持たせるニーズに応える存在として注目されています。
一方で、MDXを効果的に使いこなすにはMarkdownとReactの両方の知識が必要となり、慣れるまでは構文上の注意点やビルド設定などで戸惑うこともあるかもしれません。本記事で紹介したように基本的な仕組みとベストプラクティス、注意点を押さえておけば、MDXはきっと強力な武器になるでしょう。
最後に、MDX公式ドキュメントにはさらなる詳細情報やチュートリアルが用意されています。興味を持たれた方はぜひ公式リソースも参照しつつ、実際にMDXを使ったコンテンツ作成にチャレンジしてみてください。Markdownの気軽さにReactコンポーネントの力をプラスできるMDXを活用して、魅力的なコンテンツを創り出していきましょう!