MDX

Markdownは、テキストをフォーマットするための軽量マークアップ言語です。プレーンテキスト構文で記述し、構造的に有効なHTMLに変換できます。ウェブサイトやブログのコンテンツ作成によく使用されます。

次のように記述します...

I **love** using [Next.js](https://nextjs.org/)

出力:

<p>I <strong>love</strong> using <a href="https://nextjs.org/">Next.js</a></p>

MDXはMarkdownの拡張で、JSXをマークダウンファイル内で直接記述できます。動的なインタラクティブ性を追加したり、Reactコンポーネントをコンテンツ内に埋め込む強力な方法です。

Next.jsは、アプリケーション内のローカルMDXコンテンツと、サーバー上で動的に取得するリモートMDXファイルの両方をサポートしています。Next.jsプラグインは、マークダウンとReactコンポーネントをHTMLに変換する処理を行い、サーバーコンポーネント(App Routerのデフォルト)での使用もサポートします。

@next/mdx

@next/mdxパッケージは、Next.jsがマークダウンとMDXを処理できるように設定するために使用されます。ローカルファイルからデータを取得し、/pagesまたは/appディレクトリ内で直接.mdx拡張子のページを作成できます。

Next.jsでMDXを設定して使用する方法を見ていきましょう。

はじめに

MDXをレンダリングするために必要なパッケージをインストールします:

ターミナル
npm install @next/mdx @mdx-js/loader @mdx-js/react @types/mdx

アプリケーションのルート(app/またはsrc/の親フォルダ)にmdx-components.tsxファイルを作成します:

補足: App RouterでMDXを使用するにはmdx-components.tsxが必須であり、これがないと動作しません。

import type { MDXComponents } from 'mdx/types'

export function useMDXComponents(components: MDXComponents): MDXComponents {
  return {
    ...components,
  }
}
export function useMDXComponents(components) {
  return {
    ...components,
  }
}

プロジェクトのルートにあるnext.config.jsファイルを更新してMDXを使用するように設定します:

next.config.js
const withMDX = require('@next/mdx')()

/** @type {import('next').NextConfig} */
const nextConfig = {
  // MDXファイルを含めるように`pageExtensions`を設定
  pageExtensions: ['js', 'jsx', 'mdx', 'ts', 'tsx'],
  // オプションで他のNext.js設定を追加
}

module.exports = withMDX(nextConfig)

次に、/appディレクトリ内に新しいMDXページを作成します:

  your-project
  ├── app
  │   └── my-mdx-page
  │       └── page.mdx
  └── package.json

これで、MDXページ内でマークダウンを使用したり、Reactコンポーネントを直接インポートできます:

import { MyComponent } from 'my-components'

# Welcome to my MDX page!

This is some **bold** and _italics_ text.

This is a list in markdown:

- One
- Two
- Three

Checkout my React component:

<MyComponent />

/my-mdx-pageルートにアクセスすると、レンダリングされたMDXが表示されます。

リモートMDX

マークダウンやMDXファイルが他の場所にある場合、サーバー上で動的に取得できます。これは、別のローカルフォルダ、CMS、データベース、その他の場所に保存されているコンテンツに便利です。

MDXコンテンツを取得するための人気のあるコミュニティパッケージは次のとおりです:

注意: 慎重に進めてください。MDXはJavaScriptにコンパイルされ、サーバー上で実行されます。信頼できるソースからのみMDXコンテンツを取得する必要があります。そうしないと、リモートコード実行(RCE)につながる可能性があります。

次の例ではnext-mdx-remoteを使用しています:

import { MDXRemote } from 'next-mdx-remote/rsc'

export default async function RemoteMdxPage() {
  // MDXテキスト - ローカルファイル、データベース、CMS、フェッチなどから取得可能
  const res = await fetch('https://...')
  const markdown = await res.text()
  return <MDXRemote source={markdown} />
}
import { MDXRemote } from 'next-mdx-remote/rsc'

export default async function RemoteMdxPage() {
  // MDXテキスト - ローカルファイル、データベース、CMS、フェッチなどから取得可能
  const res = await fetch('https://...')
  const markdown = await res.text()
  return <MDXRemote source={markdown} />
}

/my-mdx-page-remoteルートにアクセスすると、レンダリングされたMDXが表示されます。

レイアウト

MDXページ間でレイアウトを共有するには、App Routerの組み込みレイアウトサポートを使用できます。

export default function MdxLayout({ children }: { children: React.ReactNode }) {
  // 共有レイアウトやスタイルを作成
  return <div style={{ color: 'blue' }}>{children}</div>
}
export default function MdxLayout({ children }) {
  // 共有レイアウトやスタイルを作成
  return <div style={{ color: 'blue' }}>{children}</div>
}

RemarkとRehypeプラグイン

オプションでremarkrehypeプラグインを提供して、MDXコンテンツを変換できます。

例えば、remark-gfmを使用してGitHub Flavored Markdownをサポートできます。

remarkrehypeエコシステムはESMのみのため、設定ファイルとしてnext.config.mjsを使用する必要があります。

next.config.mjs
import remarkGfm from 'remark-gfm'
import createMDX from '@next/mdx'

/** @type {import('next').NextConfig} */
const nextConfig = {
  // MDXファイルを含めるように`pageExtensions`を設定
  pageExtensions: ['js', 'jsx', 'mdx', 'ts', 'tsx'],
  // オプションで他のNext.js設定を追加
}

const withMDX = createMDX({
  // 必要に応じてマークダウンプラグインを追加
  options: {
    remarkPlugins: [remarkGfm],
    rehypePlugins: [],
  },
})

// MDX設定をNext.js設定とマージ
export default withMDX(nextConfig)

フロントマター

フロントマターは、ページに関するデータを保存するために使用できるYAMLのようなキー/値のペアリングです。@next/mdxはデフォルトでフロントマターをサポートしていませんが、MDXコンテンツにフロントマターを追加する多くのソリューションがあります:

@next/mdxでページメタデータにアクセスするには、.mdxファイル内からmetaオブジェクトをエクスポートできます:

export const meta = {
  author: 'John Doe',
}

# My MDX page

カスタム要素

マークダウンを使用する利点の1つは、ネイティブのHTML要素にマッピングされるため、記述が速く直感的であることです:

This is a list in markdown:

- One
- Two
- Three

上記は次のHTMLを生成します:

<p>This is a list in markdown:</p>

<ul>
  <li>One</li>
  <li>Two</li>
  <li>Three</li>
</ul>

ウェブサイトやアプリケーションに独自のスタイルを適用したい場合、ショートコードを渡すことができます。これらはHTML要素にマッピングする独自のカスタムコンポーネントです。

これを行うには、アプリケーションのルートにあるmdx-components.tsxファイルを開き、カスタム要素を追加します:

import type { MDXComponents } from 'mdx/types'
import Image from 'next/image'

// このファイルでは、MDXファイルで使用するカスタムReactコンポーネントを提供できます。
// インラインスタイル、他のライブラリからのコンポーネントなど、
// 任意のReactコンポーネントをインポートして使用できます。

export function useMDXComponents(components: MDXComponents): MDXComponents {
  return {
    // 組み込みコンポーネントをカスタマイズ(スタイル追加など)
    h1: ({ children }) => <h1 style={{ fontSize: '100px' }}>{children}</h1>,
    img: (props) => (
      <Image
        sizes="100vw"
        style={{ width: '100%', height: 'auto' }}
        {...props}
      />
    ),
    ...components,
  }
}
import Image from 'next/image'

// このファイルでは、MDXファイルで使用するカスタムReactコンポーネントを提供できます。
// インラインスタイル、他のライブラリからのコンポーネントなど、
// 任意のReactコンポーネントをインポートして使用できます。

export function useMDXComponents(components) {
  return {
    // 組み込みコンポーネントをカスタマイズ(スタイル追加など)
    h1: ({ children }) => <h1 style={{ fontSize: '100px' }}>{children}</h1>,
    img: (props) => (
      <Image
        sizes="100vw"
        style={{ width: '100%', height: 'auto' }}
        {...props}
      />
    ),
    ...components,
  }
}

詳細: マークダウンをHTMLに変換する方法

Reactはネイティブでマークダウンを理解しません。マークダウンプレーンテキストはまずHTMLに変換される必要があります。これはremarkrehypeで実現できます。

remarkはマークダウン周辺のツールエコシステムです。rehypeはHTML用の同様のエコシステムです。例えば、次のコードスニペットはマークダウンをHTMLに変換します:

import { unified } from 'unified'
import remarkParse from 'remark-parse'
import remarkRehype from 'remark-rehype'
import rehypeSanitize from 'rehype-sanitize'
import rehypeStringify from 'rehype-stringify'

main()

async function main() {
  const file = await unified()
    .use(remarkParse) // マークダウンASTに変換
    .use(remarkRehype) // HTML ASTに変換
    .use(rehypeSanitize) // HTML入力をサニタイズ
    .use(rehypeStringify) // ASTをシリアライズされたHTMLに変換
    .process('Hello, Next.js!')

  console.log(String(file)) // <p>Hello, Next.js!</p>
}

remarkrehypeエコシステムには、シンタックスハイライト見出しリンク目次生成などのプラグインが含まれます。

上記のように@next/mdxを使用する場合、remarkrehypeを直接使用する必要はありません。これらは自動的に処理されます。ここでは@next/mdxパッケージが内部で何を行っているかを深く理解するために説明しています。

RustベースのMDXコンパイラの使用(実験的)

Next.jsはRustで書かれた新しいMDXコンパイラをサポートしています。このコンパイラはまだ実験的であり、本番環境での使用は推奨されません。新しいコンパイラを使用するには、next.config.jswithMDXに渡すときに設定する必要があります:

next.config.js
module.exports = withMDX({
  experimental: {
    mdxRs: true,
  },
})

役立つリンク