nextjsreactmarkdownmdx

MDXで構築するモダンなコンテンツサイト

2025年6月28日に更新
価格
無料
✖️

はじめに:MDXとは何か?

MDXは「コンポーネント時代のためのマークダウン(Markdown for the component era)」をコンセプトにした、マークダウンファイルの内部でJSXコンポーネントを直接記述できるようにする技術です。 [5] これにより、静的な文章記述に優れたMarkdownの手軽さと、動的なUIを構築できるReactコンポーネントのインタラクティブ性をシームレスに両立できます。 [5]

例えば、以下のようにMarkdownの文章の中にReactコンポーネントを埋め込むことができます。

# 私のブログへようこそ

これはMarkdownで書かれた文章です。

<MyButton>クリックしてください!</MyButton>

インタラクティブな要素も簡単に追加できます。

Next.jsとMDXを組み合わせることで、開発者はブログ記事、ドキュメント、ポートフォリオサイトなどを、コンテンツ管理のしやすさと高度な表現力を兼ね備えた形で効率的に構築できます。 [2] Next.jsのApp Routerでは、.mdxファイルを置くだけで自動的にページとしてルーティングされ、サーバーコンポーネントとしてのレンダリングもサポートされています。 [2]

この記事では、Next.jsのApp Router環境でMDXを導入し、基本的な使い方から応用までをコード例と共に詳しく解説していきます。

1. 環境構築:Next.jsにMDXを導入する

まずは、Next.jsプロジェクトにMDXを導入するための環境を構築します。

Step 1: Next.jsプロジェクトの作成

既にプロジェクトがある場合はこのステップをスキップしてください。ない場合は、以下のコマンドで新しいNext.jsプロジェクトを作成します。

npx create-next-app@latest my-mdx-blog

Step 2: MDX関連パッケージのインストール

次に、MDXをNext.jsで利用するために必要なパッケージをインストールします。 [2]

npm install @next/mdx @mdx-js/loader @mdx-js/react @types/mdx
  • @next/mdx: Next.jsでMDXを統合するための公式プラグインです。
  • @mdx-js/loader: webpackローダーとしてMDXファイルを処理します。
  • @mdx-js/react: MDXコンテンツをReactコンポーネントとしてレンダリングするためのコアライブラリです。
  • @types/mdx: TypeScript環境での型定義を提供します。

Step 3: next.config.mjs の設定

プロジェクトのルートにあるnext.config.mjs(なければ作成)を編集し、MDXプラグインを有効にします。これにより、.mdx.md拡張子のファイルがページとして認識されるようになります。 [2]

import createMDX from '@next/mdx'

/** @type {import('next').NextConfig} */
const nextConfig = {
  // .mdxファイルをページとして認識させる
  pageExtensions: ['js', 'jsx', 'ts', 'tsx', 'md', 'mdx'],
}

const withMDX = createMDX({
  // ここにremarkやrehypeのプラグインを追加できます
  options: {
    remarkPlugins: [],
    rehypePlugins: [],
  },
})

export default withMDX(nextConfig)

これで、Next.jsプロジェクトでMDXを使う準備が整いました。

2. 基本的な使い方:MDXでページを作成する

環境が整ったので、実際にMDXを使ってページを作成してみましょう。

Step 1: インタラクティブなコンポーネントの作成

まず、MDXファイル内で使用する簡単なReactコンポーネントを作成します。componentsディレクトリを作成し、その中にCounter.tsxというファイルを作成します。

'use client'

import { useState } from 'react'

export function Counter() {
  const [count, setCount] = useState(0)

  return (
    <div style={{ border: '1px solid #ccc', padding: '16px', borderRadius: '8px' }}>
      <p>現在のカウント: {count}</p>
      <button onClick={() => setCount(count + 1)}>カウントアップ</button>
    </div>
  )
}

'use client'ディレクティブは、このコンポーネントがクライアントサイドでインタラクティブに動作するために必要です。

Step 2: MDXページの作成

appディレクトリにmy-first-postのような名前でフォルダを作成し、その中にpage.mdxというファイルを作成します。

import { Counter } from '@/components/Counter'

# MDXの最初の投稿

これはMarkdownで書かれた最初のセクションです。リストも簡単です。

- アイテム1
- アイテム2
- アイテム3

## Reactコンポーネントの埋め込み

そして、こちらがインポートしたReactコンポーネントです。
MDXの真価がここにあります。

<Counter />

このように、静的なドキュメントの中に動的なアプリケーションをシームレスに統合できます。

この状態で開発サーバーを起動(npm run dev)し、/my-first-postにアクセスすると、Markdownコンテンツとインタラクティブなカウンターコンポーネントが共に表示されることが確認できます。 [4]

3. コンポーネントのカスタマイズ:デザインを統一する

MDXの強力な機能の一つに、Markdownが変換する標準的なHTML要素(<h1>, <p>, <a>など)を、自作のReactコンポーネントに差し替える機能があります。これにより、サイト全体で一貫したデザインシステムを適用できます。

この差し替えは、mdx-components.tsxという規約ファイルを作成することで実現します。 [2]

Step 1: mdx-components.tsxの作成

プロジェクトのルート(appディレクトリと同じ階層)にmdx-components.tsxファイルを作成します。

import type { MDXComponents } from 'mdx/types'
import { ReactNode } from 'react'

export function useMDXComponents(components: MDXComponents): MDXComponents {
  return {
    // h1タグをカスタマイズ
    h1: ({ children }) => (
      <h1 style={{ fontSize: '2.5rem', color: '#1a202c', borderBottom: '2px solid #718096' }}>
        {children}
      </h1>
    ),
    // pタグをカスタマイズ
    p: ({ children }) => <p style={{ fontSize: '1.125rem', lineHeight: 1.6 }}>{children}</p>,
    // aタグをカスタマイズ
    a: ({ href, children }) => (
      <a href={href} style={{ color: '#3182ce' }} target="_blank" rel="noopener noreferrer">
        {children}
      </a>
    ),
    // 既存のコンポーネントもマージする
    ...components,
  }
}

Step 2: ルートレイアウトでの適用

App Routerを使用している場合、Next.jsは自動的にmdx-components.tsxファイルを認識し、MDXコンテンツに適用します。@next/mdxがこのファイルを自動的に使用するため、特別な設定は不要です。 [2]

この設定により、プロジェクト内のすべての.mdxファイルで<h1><p>が自動的にスタイル付けされたカスタムコンポーネントに置き換わり、デザインの一貫性が保たれます。

4. メタデータの扱い:Frontmatterを活用する

ブログ記事にはタイトル、公開日、概要といったメタデータが不可欠です。MDXでは、export構文を使ってこれらの情報を簡単に定義・利用できます。

MDXファイル内でメタデータを定義

page.mdxファイルの先頭で、metadataオブジェクトをエクスポートします。これはNext.jsのmetadata規約と統合されます。

import { Counter } from '@/components/Counter'

export const metadata = {
  title: 'MDXの最初の投稿',
  description: 'Next.jsとMDXの基本的な使い方を学びます。',
  date: '2025-06-23',
}

# {metadata.title}

*公開日: {metadata.date}*

これはMarkdownで書かれた最初のセクションです。リストも簡単です。

{/* ...以降のコンテンツ... */}

このように定義したメタデータは、Next.jsによって自動的にページの<head>タグ内に<title><meta name="description">として設定されます。また、{metadata.title}のように本文中で変数を展開して利用することも可能です。

5. 応用編:より高度な機能の実装

基本的な使い方に慣れたら、プラグインを使ってさらにMDXを強化しましょう。remark(MarkdownのASTを操作)とrehype(HTMLのASTを操作)のプラグインエコシステムを利用できます。

シンタックスハイライトの導入

コードブロックを美しく見せるために、rehype-pretty-codeを導入します。 [3]

Step 1: パッケージのインストール

npm install rehype-pretty-code shikiji

Step 2: next.config.mjsの更新 設定ファイルにrehype-pretty-codeを追加します。

import createMDX from '@next/mdx'
import rehypePrettyCode from 'rehype-pretty-code'

/** @type {import('rehype-pretty-code').Options} */
const options = {
  theme: 'one-dark-pro', // テーマを選択
  onVisitLine(node) {
    // 空行はハイライトしないようにする
    if (node.children.length === 0) {
      node.children = [{ type: 'text', value: ' ' }]
    }
  },
  onVisitHighlightedLine(node) {
    // 強調表示された行にクラスを追加
    node.properties.className.push('line--highlighted')
  },
  onVisitHighlightedWord(node) {
    // 強調表示された単語にクラスを追加
    node.properties.className = ['word--highlighted']
  },
}

/** @type {import('next').NextConfig} */
const nextConfig = {
  pageExtensions: ['js', 'jsx', 'ts', 'tsx', 'md', 'mdx'],
}

const withMDX = createMDX({
  options: {
    remarkPlugins: [],
    rehypePlugins: [[rehypePrettyCode, options]],
  },
})

export default withMDX(nextConfig)

これで、MDXファイル内のコードブロックが自動的にシンタックスハイライトされるようになります。

この記事はいかがでしたか?

他にも様々な記事や本をご用意しています。ぜひチェックしてみてください🤩