Next.js エバンジェリストの視点から、MDXをプロジェクトに組み込み、コンテンツを「体験」へと昇華させるための最短経路を提示します。この記事では、App Router環境での正確な実装手順と、最新のNext.js 15/16で直面する高度な「落とし穴」を先回りして解決します。
--------------------------------------------------------------------------------
1. MDXとは:コンテンツとUIが融合する新世界
MDXの本質は、**「Markdown + JSX(Reactコンポーネント)」**です。 単なる「静的な装飾テキスト」を、動的なインターフェースへと変貌させる魔法の道具といえます。
MDXを導入する「3つの本質的なメリット」
- [ ] コンテンツとUIの完全な一体化:記事の文脈に合わせて「購入ボタン」や「インタラクティブなチャート」を自在に配置可能。
- [ ] CMSに縛られない柔軟な表現:外部CMSのスキーマ制約から解放され、Reactで書けるものなら何でもドキュメント内に埋め込める。
- [ ] 高度なコンテンツ設計:商品カードや警告バナーなどのUIをコンポーネント化し、複数の記事間で一貫性を保ちながら再利用できる。
通常のMarkdown vs MDX 比較表
| 機能 | 通常のMarkdown (.md) | MDX (.mdx) |
| コンポーネントの埋め込み | 不可(ただの文字列として表示) | 可能(<Button /> 等を直接記述) |
| ロジックの記述 | 不可 | 可能(JavaScriptの変数や計算が可能) |
| スタイリング | CSS/HTMLに依存 | Reactコンポーネント経由で自由に制御 |
| 主なユースケース | シンプルなドキュメント、メモ | 豊かな表現を必要とするブログ、LP、ダッシュボード |
移行文: 次のセクションでは、この強力な機能を動かすための土台作り、パッケージのインストールから始めます。
--------------------------------------------------------------------------------
2. 環境構築フェーズ:依存パッケージのインストール
App Router環境では、ビルド時と実行時のどちらでMDXを処理するかによって必要な道具が変わります。
主要パッケージの役割一覧
| パッケージ名 | 役割 | 必須度 |
@next/mdx | Next.jsにMDXを統合。.mdxをページとして扱う。 | 必須 |
@mdx-js/loader | Webpack/TurbopackでMDXを解析するためのローダー。 | 必須 |
@mdx-js/react | ReactでMDXをレンダリングするためのコアライブラリ。 | 必須 |
@types/mdx | TypeScript環境でのMDXファイルの型定義を提供。 | 推奨 |
インストールコマンド
npm install @next/mdx @mdx-js/loader @mdx-js/react @types/mdx
【ハマりポイント①:パッケージの重複と使い分け】
初心者が最も混乱するのが、@next/mdx と next-mdx-remote-client の違いです。
@next/mdx(静的・ビルド時):プロジェクト内のファイルをコンパイルします。app/内に置いたMDXがそのままURLになります。next-mdx-remote-client(動的・ランタイム時):外部DBやAPI、ファイルシステムから「文字列」として取得したMDXを、実行時にRSC(Server Component)で変換します。動的なブログ投稿に必須です。
移行文: 道具が揃ったら、次はNext.jsに「MDXファイルを認識させる」ための設定に移ります。
--------------------------------------------------------------------------------
3. 設定フェーズ:next.config.mjs の構築
プロジェクトの心臓部で、.mdxを有効化します。
next.config.mjs の設定例
import createMDX from '@next/mdx'
import remarkGfm from 'remark-gfm'
import rehypePrettyCode from 'rehype-pretty-code'
/** @type {import('next').NextConfig} */
const nextConfig = {
// MDXファイルをページやインポート対象として認識させる
pageExtensions: ['js', 'jsx', 'md', 'mdx', 'ts', 'tsx'],
}
const withMDX = createMDX({
options: {
remarkPlugins: [remarkGfm],
rehypePlugins: [rehypePrettyCode], // シンタックスハイライト(後述)
},
})
export default withMDX(nextConfig)
【ハマりポイント②:ESM形式の制約】
重要: 必ず拡張子を .mjs にしてください。Remark/Rehypeのエコシステムは現在 ESM専用 です。従来の .js (CommonJS) を使うと、プラグインの読み込み時に「require() of ES Module」エラーが発生し、プロジェクトが起動しなくなります。
また、pageExtensions に 'mdx' を追加し忘れると、app/page.mdx を作成しても 404 エラーになるため、忘れずに記述しましょう。
移行文: 設定が完了しても、App Routerには「もう一つだけ」欠かせないファイルがあります。
--------------------------------------------------------------------------------
4. 必須コンポーネント:mdx-components.tsx の配置
App RouterでMDXを動作させるための「儀式」として、mdx-components.tsx の配置が必須です。
配置ディレクトリ(Tree構造)
プロジェクトの構造に合わせて、以下のいずれか(通常は src 直下)に配置します。
my-next-app/
├── src/
│ ├── app/
│ └── mdx-components.tsx <-- ここ!
├── next.config.mjs
└── package.json
最小限の構成コード
このファイルは、MDX内のHTMLタグ(h1, pなど)をどのReactコンポーネントで描画するかを決定するハブになります。
import type { MDXComponents } from 'mdx/types'
export function useMDXComponents(components: MDXComponents): MDXComponents {
return {
// 既存のコンポーネントを継承
...components,
// グローバルに適用したいカスタムコンポーネントを定義
h1: ({ children }) => <h1 className="text-3xl font-bold">{children}</h1>,
Button: ({ children }) => <button className="bg-blue-500 p-2">{children}</button>,
}
}
【超重要:二重管理の罠】
もし next-mdx-remote-client を併用する場合、mdx-components.tsx で定義したコンポーネントは MDXRemote には自動適用されません。
@next/mdx用:mdx-components.tsxで定義next-mdx-remote-client用:MDXRemoteのcomponentsプロパティに個別に渡す という「二重管理」が必要になることを覚えておいてください。
移行文: これで全ての準備が整いました。いよいよMDXを実際に画面に表示させてみましょう。
--------------------------------------------------------------------------------
5. レンダリング実践:3つの表示戦略
① ファイルベースルーティング
app/about/page.mdx を作成するだけで、自動的に /about が生成されます。固定ページに最適です。
② インポートによる利用
.tsx ページ内で、MDXをコンポーネントとして呼び出します。
import Welcome from './welcome.mdx'
export default function Page() { return <Welcome /> }
③ Dynamic Routesでの利用
compileMDX を使い、ファイルシステムから動的に読み込みます。
import { compileMDX } from 'next-mdx-remote/rsc'
// ...ファイルを読み込んで compileMDX で変換
【ハマりポイント③:Next.js 15+ の params 非同期化】
最新のNext.jsにおける最大の破壊的変更です。params は Promise になりました。
// ❌ 修正前:ビルドは通るが、実行時に undefined になり崩壊する(型エラーが出ない場合があり危険!)
export default function Page({ params }: { params: { slug: string } }) {
const { slug } = params; // paramsはPromiseなのでdestructureできない
}
// ✅ 修正後:await が必須。generateMetadata も同様です。
export default async function Page(props: { params: Promise<{ slug: string }> }) {
const params = await props.params;
const slug = params.slug;
}
移行文: コンテンツが表示されたら、次は読みやすさを追求するためのスタイリングです。
--------------------------------------------------------------------------------
6. スタイリングと拡張:Tailwind Typography とプラグイン
Tailwind Typography による一括スタイリング
| 設定項目 | メリット |
prose クラス付与 | 記事内の h1, ul, blockquote 等の余白とサイズを自動調整。 |
layout.tsx での継承 | 記事エリアを <div className="prose"> で囲むだけで全記事に適用。 |
推奨プラグイン:Shiki の魔法
シンタックスハイライトには rehype-pretty-code を強く推奨します。これは VS Code と同じエンジン(Shiki)を使用しているため、Prism よりも圧倒的にハイライトの精度が高く、テーマの互換性も抜群です。
【ハマりポイント④:mdxRs (Rustコンパイラ) の現状】
next.config.mjs で mdxRs: true を設定するとビルドは高速化されますが、実験的機能です。remark-gfm などの JavaScript ベースのプラグインと競合してエラーを吐くことが多いため、高度なプラグインを使う場合は false にしておきましょう。
--------------------------------------------------------------------------------
7. 高度な最適化:画像・メタデータ・SEO
フロントマターとメタデータの分離
gray-matter を使用して data (YAML) と content (本文) を分離する手法が王道です。取得した data は、Next.js の generateMetadata に渡して SEO を最適化しましょう。
next/image の自動適用:ハッシュ・ハック
MDX の img タグで画像サイズを指定できない問題は、URLにサイズを仕込むことで解決します。
- MDX内で画像を
と記述。 mdx-components.tsx内でimgコンポーネントを定義。src.split('#')[1]で800と600を取り出し、next/imageのwidth,heightに割り当てる。
【ハマりポイント⑤:Edge Runtime の制約】
OG画像の自動生成などで Edge Runtime を使う場合、以下の制約に注意してください。
- Node.js API 不可:
fsやpathは使えません。 - CSS制約:
ImageResponseで使える CSS は Flexbox のサブセットのみです(Grid やposition: absoluteは使用不可)。 - 日本語豆腐問題: 日本語フォントを明示的に
fetchしてロードしないと、文字が「□(豆腐)」になります。 - fetchキャッシュ: フォント取得には
cache: 'force-cache'を付与し、毎リクエストの外部アクセスを防ぎましょう。
--------------------------------------------------------------------------------
8. まとめ:スムーズなMDXライフのために
導入チェックリスト
- [ ] パッケージのインストール (
@next/mdx,@mdx-js/loader, etc.) - [ ]
next.config.mjsでpageExtensionsとプラグインの設定 - [ ] プロジェクトルートまたは
src/直下にmdx-components.tsxを配置 - [ ] Next.js 15+ ならば
paramsの非同期処理 (await) を徹底 - [ ]
@tailwindcss/typographyで.proseクラスを適用
学習リソース
- Next.js 公式 MDX ドキュメント
- Portfolio Starter Kit (Vercel公式テンプレート)
- Rehype Pretty Code Docs (Shikiの設定詳細)
MDXは、単なるドキュメント形式ではなく、あなたのWebサイトを強力な「アプリケーション」へと進化させる核となります。このワークフローを基に、最高のエクスペリエンスを構築してください。