Next.js App Router × MDX 導入・完全ワークフロー

2026年04月02日

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/mdxNext.jsにMDXを統合。.mdxをページとして扱う。必須
@mdx-js/loaderWebpack/TurbopackでMDXを解析するためのローダー。必須
@mdx-js/reactReactでMDXをレンダリングするためのコアライブラリ。必須
@types/mdxTypeScript環境でのMDXファイルの型定義を提供。推奨

インストールコマンド

npm install @next/mdx @mdx-js/loader @mdx-js/react @types/mdx

【ハマりポイント①:パッケージの重複と使い分け】

初心者が最も混乱するのが、@next/mdxnext-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: MDXRemotecomponents プロパティに個別に渡す という「二重管理」が必要になることを覚えておいてください。

移行文: これで全ての準備が整いました。いよいよ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における最大の破壊的変更です。paramsPromise になりました。

// ❌ 修正前:ビルドは通るが、実行時に 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.mjsmdxRs: true を設定するとビルドは高速化されますが、実験的機能です。remark-gfm などの JavaScript ベースのプラグインと競合してエラーを吐くことが多いため、高度なプラグインを使う場合は false にしておきましょう。

--------------------------------------------------------------------------------

7. 高度な最適化:画像・メタデータ・SEO

フロントマターとメタデータの分離

gray-matter を使用して data (YAML) と content (本文) を分離する手法が王道です。取得した data は、Next.js の generateMetadata に渡して SEO を最適化しましょう。

next/image の自動適用:ハッシュ・ハック

MDX の img タグで画像サイズを指定できない問題は、URLにサイズを仕込むことで解決します。

  1. MDX内で画像を ![alt](path/to/image.png#800_600) と記述。
  2. mdx-components.tsx 内で img コンポーネントを定義。
  3. src.split('#')[1]800600 を取り出し、next/imagewidth, height に割り当てる。

【ハマりポイント⑤:Edge Runtime の制約】

OG画像の自動生成などで Edge Runtime を使う場合、以下の制約に注意してください。

  • Node.js API 不可: fspath は使えません。
  • CSS制約: ImageResponse で使える CSS は Flexbox のサブセットのみです(Grid や position: absolute は使用不可)。
  • 日本語豆腐問題: 日本語フォントを明示的に fetch してロードしないと、文字が「□(豆腐)」になります。
  • fetchキャッシュ: フォント取得には cache: 'force-cache' を付与し、毎リクエストの外部アクセスを防ぎましょう。

--------------------------------------------------------------------------------

8. まとめ:スムーズなMDXライフのために

導入チェックリスト

  • [ ] パッケージのインストール (@next/mdx, @mdx-js/loader, etc.)
  • [ ] next.config.mjspageExtensions とプラグインの設定
  • [ ] プロジェクトルートまたは src/ 直下に mdx-components.tsx を配置
  • [ ] Next.js 15+ ならば params の非同期処理 (await) を徹底
  • [ ] @tailwindcss/typography.prose クラスを適用

学習リソース

MDXは、単なるドキュメント形式ではなく、あなたのWebサイトを強力な「アプリケーション」へと進化させる核となります。このワークフローを基に、最高のエクスペリエンスを構築してください。

最新のお知らせ

thumb
2026年4月2日
MDXレンダリング最適化および高機能コンポーネント実装要件定義書

1. プロジェクトの背景と戦略的意義 モダンなWebフロントエ...

No Image
2026年4月2日
Next.js App Router × MDX 導入・完全ワークフロー

Next.js エバンジェリストの視点から、MDXをプロジェクトに...

thumb
2026年4月2日
【新常識】MarkdownとReactが融合する「MDX」の世界:記事の中でアプリが動く魔法

1. はじめに:なぜ今、MDXが必要なのか? プログラミン...

thumb
2026年4月1日
多拠点展開の「正解」がここにある。次世代ポータル基盤『Plus1 Community』から学ぶ5つの設計思想

1. イントロダクション:多拠点管理の「カオス」を解き明か...

thumb
2026年3月31日
アイプラスワンのホームページトップに、ECサイト基盤とコミュニケーションサイト基盤をのせたい

いいですね、その方向はかなり“刺さる”構成になります。今やる...

thumb
2026年3月30日
WindowsでのDocker開発を劇的に変える、5つの「戦略的」最適化術と真実

WindowsプラットフォームにおけるDocker開発の歴史は、仮想化技...

thumb
2026年3月29日
1つの方程式で、あらゆる「つながり」を。マッチング基盤設計に学ぶ、究極の再利用戦略

1. イントロダクション:マッチングサイト乱立時代の「車輪...

thumb
2026年3月26日
Beyond the Hammer: Rediscovering the Joy of Building

このブログ記事は、開発者のコミュニティや職場において人工知...

thumb
2026年3月26日
モダンECプラットフォーム「ec-plus1」構造完全ガイド

このドキュメントでは、最新のECプラットフォーム「ec-plus...

thumb
2026年3月25日
3日でEC基盤を構築する:AI時代の超速開発と「譲れない設計」の裏側

開発期間わずか3日間。この驚異的なスピードで、マルチテナ...