MarkdownとMDX

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

例えば次のように記述すると...

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

次のHTMLが出力されます:

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

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

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

@next/mdx

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

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

はじめに

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

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

プロジェクトのルートにある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)

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

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

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

import { MyComponent } from 'my-components'

# 私のMDXページへようこそ!

これは**太字**と_斜体_のテキストです。

Markdownでのリスト:

- 一つ
- 二つ
- 三つ

Reactコンポーネントをチェック:

<MyComponent />

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

リモートMDX

MarkdownやMDXファイルが別の場所にある場合、サーバー上で動的に取得できます。これは、別のローカルフォルダ、CMS、データベース、その他の場所に保存されているコンテンツに便利です。この用途で人気のあるコミュニティパッケージはnext-mdx-remoteです。

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

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

import { serialize } from 'next-mdx-remote/serialize'
import { MDXRemote, MDXRemoteSerializeResult } from 'next-mdx-remote'

interface Props {
  mdxSource: MDXRemoteSerializeResult
}

export default function RemoteMdxPage({ mdxSource }: Props) {
  return <MDXRemote {...mdxSource} />
}

export async function getStaticProps() {
  // MDXテキスト - ローカルファイル、データベース、CMS、fetchなどから取得可能
  const res = await fetch('https:...')
  const mdxText = await res.text()
  const mdxSource = await serialize(mdxText)
  return { props: { mdxSource } }
}
import { serialize } from 'next-mdx-remote/serialize'
import { MDXRemote } from 'next-mdx-remote'

export default function RemoteMdxPage({ mdxSource }) {
  return <MDXRemote {...mdxSource} />
}

export async function getStaticProps() {
  // MDXテキスト - ローカルファイル、データベース、CMS、fetchなどから取得可能
  const res = await fetch('https:...')
  const mdxText = await res.text()
  const mdxSource = await serialize(mdxText)
  return { props: { mdxSource } }
}

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

レイアウト

MDXページ周囲でレイアウトを共有するには、レイアウトコンポーネントを作成します:

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>
}

次に、レイアウトコンポーネントをMDXページにインポートし、MDXコンテンツをレイアウトでラップしてエクスポートします:

import MdxLayout from '../components/mdx-layout'

# 私のMDXページへようこそ!

export default function MDXPage({ children }) {
  return <MdxLayout>{children}</MdxLayout>

}

RemarkとRehypeプラグイン

必要に応じて、MDXコンテンツを変換するためにremarkrehypeプラグインを提供できます。

例えば、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({
  // 必要に応じてMarkdownプラグインを追加
  options: {
    remarkPlugins: [remarkGfm],
    rehypePlugins: [],
  },
})

// MDXとNext.js設定を相互にラップ
export default withMDX(nextConfig)

フロントマター

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

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

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

# 私のMDXページ

カスタム要素

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

これはMarkdownでのリストです:

- 一つ
- 二つ
- 三つ

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

<p>これはMarkdownでのリストです:</p>

<ul>
  <li>一つ</li>
  <li>二つ</li>
  <li>三つ</li>
</ul>

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

これを行うには、アプリケーションのルート(pages/またはsrc/の親フォルダ)にmdx-components.tsxファイルを作成し、カスタム要素を追加します:

import type { MDXComponents } from 'mdx/types'
import Image, { ImageProps } 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 as ImageProps)}
      />
    ),
    ...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,
  }
}

詳細: MarkdownをHTMLに変換する方法

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

remarkはMarkdown周辺のツールエコシステムです。rehypeはHTML用の同様のエコシステムです。例えば、次のコードスニペットはMarkdownを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) // Markdown 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コンパイラをサポートしています。このコンパイラはまだ実験的で、本番環境での使用は推奨されていません。新しいコンパイラを使用するには、withMDXに渡すときにnext.config.jsを設定する必要があります:

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

役立つリンク