Next.jsで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に変換する処理を行い、App RouterのデフォルトであるServer Componentsでの使用もサポートしています。
豆知識: 完全な動作例についてはPortfolio Starter Kitテンプレートをご覧ください。
依存関係のインストール
@next/mdx
パッケージと関連パッケージは、Next.jsがMarkdownとMDXを処理できるように設定するために使用されます。ローカルファイルからデータを取得するため、/pages
または/app
ディレクトリに直接.md
または.mdx
拡張子のページを作成できます。
Next.jsでMDXをレンダリングするには、次のパッケージをインストールします:
npm install @next/mdx @mdx-js/loader @mdx-js/react @types/mdx
next.config.mjs
の設定
プロジェクトのルートにあるnext.config.mjs
ファイルを更新して、MDXを使用するように設定します:
import createMDX from '@next/mdx'
/** @type {import('next').NextConfig} */
const nextConfig = {
// markdownとMDXファイルを含めるように`pageExtensions`を設定
pageExtensions: ['js', 'jsx', 'md', 'mdx', 'ts', 'tsx'],
// 必要に応じて他のNext.js設定を追加
}
const withMDX = createMDX({
// 必要に応じてmarkdownプラグインを追加
})
// MDX設定をNext.js設定にマージ
export default withMDX(nextConfig)
これにより、.mdx
ファイルをアプリケーション内のページ、ルート、またはインポートとして使用できるようになります。
.md
ファイルの処理
デフォルトでは、next/mdx
は.mdx
拡張子のファイルのみをコンパイルします。.md
ファイルをwebpackで処理するには、extension
オプションを更新します:
const withMDX = createMDX({
extension: /\.(md|mdx)$/,
})
豆知識: Turbopackは現在
extension
オプションをサポートしておらず、したがって.md
ファイルもサポートしていません。
mdx-components.tsx
ファイルの追加
プロジェクトのルートにmdx-components.tsx
(または.js
)ファイルを作成して、グローバルなMDXコンポーネントを定義します。例えば、pages
やapp
と同じレベル、または該当する場合はsrc
内に配置します。
import type { MDXComponents } from 'mdx/types'
export function useMDXComponents(components: MDXComponents): MDXComponents {
return {
...components,
}
}
export function useMDXComponents(components) {
return {
...components,
}
}
豆知識:
mdx-components.tsx
はApp Routerで@next/mdx
を使用するために必須であり、これがないと動作しません。mdx-components.tsx
ファイル規約について詳しく学びましょう。- カスタムスタイルとコンポーネントの使用方法を学びましょう。
MDXのレンダリング
MDXは、Next.jsのファイルベースルーティングを使用するか、MDXファイルを他のページにインポートすることでレンダリングできます。
ファイルベースルーティングの使用
ファイルベースルーティングを使用する場合、MDXページは他のページと同じように使用できます。
/pages
ディレクトリ内に新しいMDXページを作成します:
my-project
|── mdx-components.(tsx/js)
├── pages
│ └── mdx-page.(mdx/md)
└── package.json
これらのファイルでMDXを使用し、Reactコンポーネントを直接MDXページ内にインポートできます:
import { MyComponent } from 'my-component'
# 私のMDXページへようこそ!
これは**太字**と*斜体*のテキストです。
これはMarkdownのリストです:
- 1つ目
- 2つ目
- 3つ目
私のReactコンポーネントをチェックしてください:
<MyComponent />
/mdx-page
ルートに移動すると、レンダリングされたMDXページが表示されます。
インポートの使用
/pages
ディレクトリ内に新しいページを作成し、任意の場所にMDXファイルを作成します:
.
├── markdown/
│ └── welcome.(mdx/md)
├── pages/
│ └── mdx-page.(tsx/js)
├── mdx-components.(tsx/js)
└── package.json
これらのファイルでMDXを使用し、Reactコンポーネントを直接MDXページ内にインポートできます:
import { MyComponent } from 'my-component'
# 私のMDXページへようこそ!
これは**太字**と*斜体*のテキストです。
これはMarkdownのリストです:
- 1つ目
- 2つ目
- 3つ目
私のReactコンポーネントをチェックしてください:
<MyComponent />
MDXファイルをページ内にインポートしてコンテンツを表示します:
import Welcome from '@/markdown/welcome.mdx'
export default function Page() {
return <Welcome />
}
import Welcome from '@/markdown/welcome.mdx'
export default function Page() {
return <Welcome />
}
/mdx-page
ルートに移動すると、レンダリングされたMDXページが表示されます。
カスタムスタイルとコンポーネントの使用
Markdownはレンダリング時にネイティブHTML要素にマッピングされます。例えば、次のMarkdownを記述すると:
## これは見出しです
これはMarkdownのリストです:
- 1つ目
- 2つ目
- 3つ目
次のHTMLが生成されます:
<h2>これは見出しです</h2>
<p>これはMarkdownのリストです:</p>
<ul>
<li>1つ目</li>
<li>2つ目</li>
<li>3つ目</li>
</ul>
Markdownにスタイルを適用するには、生成されたHTML要素にマッピングするカスタムコンポーネントを提供できます。スタイルとコンポーネントは、グローバル、ローカル、および共有レイアウトで実装できます。
グローバルスタイルとコンポーネント
mdx-components.tsx
にスタイルとコンポーネントを追加すると、アプリケーション内のすべてのMDXファイルに影響します。
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={{ color: 'red', fontSize: '48px' }}>{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={{ color: 'red', fontSize: '48px' }}>{children}</h1>
),
img: (props) => (
<Image
sizes="100vw"
style={{ width: '100%', height: 'auto' }}
{...props}
/>
),
...components,
}
}
ローカルスタイルとコンポーネント
インポートしたMDXコンポーネントにスタイルやコンポーネントを渡すことで、特定のページにローカルスタイルやコンポーネントを適用できます。これらはグローバルスタイルとコンポーネントとマージされ、上書きされます。
import Welcome from '@/markdown/welcome.mdx'
function CustomH1({ children }) {
return <h1 style={{ color: 'blue', fontSize: '100px' }}>{children}</h1>
}
const overrideComponents = {
h1: CustomH1,
}
export default function Page() {
return <Welcome components={overrideComponents} />
}
import Welcome from '@/markdown/welcome.mdx'
function CustomH1({ children }) {
return <h1 style={{ color: 'blue', fontSize: '100px' }}>{children}</h1>
}
const overrideComponents = {
h1: CustomH1,
}
export default function Page() {
return <Welcome components={overrideComponents} />
}
共有レイアウト
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>
}
Tailwind タイポグラフィプラグインの使用
アプリケーションのスタイリングに Tailwind を使用している場合、@tailwindcss/typography
プラグイン を使用すると、マークダウンファイルで Tailwind の設定とスタイルを再利用できます。
このプラグインは prose
クラスのセットを追加し、マークダウンのようなソースから来るコンテンツブロックにタイポグラフィスタイルを適用できます。
Tailwind タイポグラフィをインストール し、共有レイアウト と共に使用して必要な prose
を追加します。
MDX ページ間でレイアウトを共有するには、レイアウトコンポーネントを作成します:
export default function MdxLayout({ children }: { children: React.ReactNode }) {
// 共有レイアウトやスタイルをここで作成
return (
<div className="prose prose-headings:mt-8 prose-headings:font-semibold prose-headings:text-black prose-h1:text-5xl prose-h2:text-4xl prose-h3:text-3xl prose-h4:text-2xl prose-h5:text-xl prose-h6:text-lg dark:prose-headings:text-white">
{children}
</div>
)
}
export default function MdxLayout({ children }) {
// 共有レイアウトやスタイルをここで作成
return (
<div className="prose prose-headings:mt-8 prose-headings:font-semibold prose-headings:text-black prose-h1:text-5xl prose-h2:text-4xl prose-h3:text-3xl prose-h4:text-2xl prose-h5:text-xl prose-h6:text-lg dark:prose-headings:text-white">
{children}
</div>
)
}
次に、レイアウトコンポーネントを MDX ページにインポートし、MDX コンテンツをレイアウトでラップしてエクスポートします:
import MdxLayout from '../components/mdx-layout'
# 私の MDX ページへようこそ!
export default function MDXPage({ children }) {
return <MdxLayout>{children}</MdxLayout>
}
フロントマター
フロントマターは YAML のようなキー/値のペアで、ページに関するデータを保存するために使用できます。@next/mdx
はデフォルトでフロントマターを サポートしていません が、以下のような多くのソリューションがあります:
@next/mdx
は他の JavaScript コンポーネントと同様にエクスポートを使用できます:
export const metadata = {
author: 'John Doe',
}
# ブログ投稿
これで、MDX ファイルの外部でメタデータを参照できます:
import BlogPost, { metadata } from '@/content/blog-post.mdx'
export default function Page() {
console.log('metadata: ', metadata)
//=> { author: 'John Doe' }
return <BlogPost />
}
import BlogPost, { metadata } from '@/content/blog-post.mdx'
export default function Page() {
console.log('metadata: ', metadata)
//=> { author: 'John Doe' }
return <BlogPost />
}
この機能の一般的な使用例は、MDX のコレクションを反復処理してデータを抽出する場合です。例えば、すべてのブログ投稿からブログインデックスページを作成する場合です。Node の fs
モジュール や globby などのパッケージを使用して、投稿ディレクトリを読み込み、メタデータを抽出できます。
知っておくと便利:
fs
、globby
などの使用はサーバーサイドでのみ可能です。- 完全な動作例は Portfolio Starter Kit テンプレートを参照してください。
remark と rehype プラグイン
必要に応じて、MDX コンテンツを変換する remark と rehype プラグインを提供できます。
例えば、remark-gfm
を使用して GitHub Flavored Markdown をサポートできます。
remark と rehype のエコシステムは ESM のみであるため、設定ファイルとして next.config.mjs
または next.config.ts
を使用する必要があります。
import remarkGfm from 'remark-gfm'
import createMDX from '@next/mdx'
/** @type {import('next').NextConfig} */
const nextConfig = {
// .mdx 拡張子を許可
pageExtensions: ['js', 'jsx', 'md', 'mdx', 'ts', 'tsx'],
// 必要に応じて他の Next.js 設定を追加
}
const withMDX = createMDX({
// 必要に応じてマークダウンプラグインを追加
options: {
remarkPlugins: [remarkGfm],
rehypePlugins: [],
},
})
// MDX と Next.js の設定を結合
export default withMDX(nextConfig)
Turbopack でプラグインを使用する
Turbopack でプラグインを使用するには、最新の @next/mdx
にアップグレードし、プラグイン名を文字列で指定します:
import createMDX from '@next/mdx'
/** @type {import('next').NextConfig} */
const nextConfig = {
pageExtensions: ['js', 'jsx', 'md', 'mdx', 'ts', 'tsx'],
}
const withMDX = createMDX({
options: {
remarkPlugins: [],
rehypePlugins: [['rehype-katex', { strict: true, throwOnError: true }]],
},
})
export default withMDX(nextConfig)
知っておくと便利:
シリアライズ可能なオプションを持たない remark と rehype プラグインは、JavaScript 関数を Rust に渡せない ため、Turbopack ではまだ使用できません。
リモート MDX
MDX ファイルやコンテンツが 別の場所 にある場合、サーバー上で動的に取得できます。これは CMS、データベース、その他の場所に保存されたコンテンツに便利です。この用途のコミュニティパッケージとして next-mdx-remote-client
があります。
知っておくと便利: 注意して進めてください。MDX は JavaScript にコンパイルされ、サーバー上で実行されます。信頼できるソースからのみ MDX コンテンツを取得する必要があります。そうしないと、リモートコード実行 (RCE) につながる可能性があります。
以下の例では next-mdx-remote-client
を使用しています:
import {
serialize,
type SerializeResult,
} from 'next-mdx-remote-client/serialize'
import { MDXClient } from 'next-mdx-remote-client'
type Props = {
mdxSource: SerializeResult
}
export default function RemoteMdxPage({ mdxSource }: Props) {
if ('error' in mdxSource) {
// エラー UI をレンダリングするか `mdxSource.error` をスロー
}
return <MDXClient {...mdxSource} />
}
export async function getStaticProps() {
// MDX テキスト - データベース、CMS、fetch、どこからでも...
const res = await fetch('https:...')
const mdxText = await res.text()
const mdxSource = await serialize({ source: mdxText })
return { props: { mdxSource } }
}
import { serialize } from 'next-mdx-remote-client/serialize'
import { MDXClient } from 'next-mdx-remote-client'
export default function RemoteMdxPage({ mdxSource }) {
if ('error' in mdxSource) {
// エラー UI をレンダリングするか `mdxSource.error` をスロー
}
return <MDXClient {...mdxSource} />
}
export async function getStaticProps() {
// MDX テキスト - データベース、CMS、fetch、どこからでも...
const res = await fetch('https:...')
const mdxText = await res.text()
const mdxSource = await serialize({ source: mdxText })
return { props: { mdxSource } }
}
/mdx-page-remote
ルートに移動すると、レンダリングされた MDX が表示されます。
詳細: マークダウンを HTML に変換する方法
React はネイティブでマークダウンを理解しません。マークダウンプレーンテキストはまず HTML に変換する必要があります。これは remark
と rehype
で実現できます。
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>
}
remark
と rehype
のエコシステムには、シンタックスハイライト、見出しリンク、目次生成 などのプラグインがあります。
上記のように @next/mdx
を使用する場合、直接 remark
や rehype
を使用する必要はありません。これらは内部で処理されます。ここでは @next/mdx
パッケージの内部動作を理解するために説明しています。
Rust ベースの MDX コンパイラの使用 (実験的)
Next.js は Rust で書かれた新しい MDX コンパイラをサポートしています。このコンパイラはまだ実験的で、本番環境での使用は推奨されません。新しいコンパイラを使用するには、next.config.js
を withMDX
に渡すときに設定する必要があります:
module.exports = withMDX({
experimental: {
mdxRs: true,
},
})
mdxRs
は MDX ファイルの変換方法を設定するオブジェクトも受け入れます。
module.exports = withMDX({
experimental: {
mdxRs: {
jsxRuntime?: string // カスタム jsx ランタイム
jsxImportSource?: string // カスタム jsx インポートソース
mdxType?: 'gfm' | 'commonmark' // 解析と変換に使用する MDX 構文の種類を設定
},
},
})