Next.js で Markdown と MDX を使用する方法
Markdown は、テキストをフォーマットするために使用される軽量マークアップ言語です。プレーンテキスト構文を使用して記述し、構造的に有効な HTML に変換できます。ウェブサイトやブログでコンテンツを記述するためによく使用されます。
あなたは書く...
I **love** using [Next.js](https://nextjs.dokyumento.jp/)出力
<p>I <strong>love</strong> using <a href="https://nextjs.dokyumento.jp/">Next.js</a></p>MDX は Markdown のスーパーセットであり、Markdown ファイル内で直接 JSX を記述することができます。コンテンツ内に動的なインタラクティブ性や React コンポーネントを埋め込むための強力な方法です。
Next.js は、アプリケーション内のローカル MDX コンテンツと、サーバーで動的に取得されるリモート MDX ファイルの両方をサポートできます。Next.js プラグインは、Markdown と React コンポーネントを HTML に変換する処理を行い、Server Components (App Router のデフォルト) での使用もサポートします。
知っておくと便利:完全な動作例については、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/mdxnext.config.mjs の設定
MDX を使用するように設定するには、プロジェクトのルートにある next.config.mjs ファイルを更新してください。
import createMDX from '@next/mdx'
/** @type {import('next').NextConfig} */
const nextConfig = {
// Configure `pageExtensions` to include markdown and MDX files
pageExtensions: ['js', 'jsx', 'md', 'mdx', 'ts', 'tsx'],
// Optionally, add any other Next.js config below
}
const withMDX = createMDX({
// Add markdown plugins here, as desired
})
// Merge MDX config with Next.js config
export default withMDX(nextConfig)これにより、.mdx ファイルをアプリケーション内のページ、ルート、またはインポートとして使用できるようになります。
.md ファイルの処理
デフォルトでは、next/mdx は .mdx 拡張子のファイルのみをコンパイルします。webpack で .md ファイルを処理するには、extension オプションを更新してください。
const withMDX = createMDX({
extension: /\.(md|mdx)$/,
})mdx-components.tsx ファイルの追加
プロジェクトのルートに mdx-components.tsx (または .js) ファイルを作成して、グローバル MDX コンポーネントを定義します。例えば、pages または app と同じレベル、または該当する場合は src 内に配置します。
import type { MDXComponents } from 'mdx/types'
const components: MDXComponents = {}
export function useMDXComponents(): MDXComponents {
return components
}知っておくと良いこと:
- App Router で
@next/mdxを使用するにはmdx-components.tsxが必須であり、これがないと動作しません。mdx-components.tsxファイル規約について詳しくはこちらをご覧ください。- カスタムスタイルとコンポーネントの使用方法 についてはこちらをご覧ください。
MDX のレンダリング
Next.js のファイルベースルーティングを使用して MDX をレンダリングしたり、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'
# 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 />/mdx-page ルートに移動すると、レンダリングされた MDX ページが表示されるはずです。
インポートの使用
/pages ディレクトリ内に新しいページを作成し、好きな場所に MDX ファイルを作成します。
.
├── markdown/
│ └── welcome.(mdx/md)
├── pages/
│ └── mdx-page.(tsx/js)
├── mdx-components.(tsx/js)
└── package.jsonこれらのファイル内で MDX を使用したり、React コンポーネントを MDX ページ内に直接インポートしたりすることもできます。
ページ内で MDX ファイルをインポートして、コンテンツを表示します。
import Welcome from '@/markdown/welcome.mdx'
export default function Page() {
return <Welcome />
}/mdx-page ルートに移動すると、レンダリングされた MDX ページが表示されるはずです。
カスタムスタイルとコンポーネントの使用
Markdown は、レンダリングされるとネイティブな HTML 要素にマッピングされます。例えば、次の Markdown を記述すると
## This is a heading
This is a list in markdown:
- One
- Two
- Three次の HTML が生成されます
<h2>This is a heading</h2>
<p>This is a list in markdown:</p>
<ul>
<li>One</li>
<li>Two</li>
<li>Three</li>
</ul>Markdown をスタイル設定するには、生成された HTML 要素にマッピングされるカスタムコンポーネントを提供できます。スタイルとコンポーネントは、グローバル、ローカル、および共有レイアウトで実装できます。
グローバルスタイルとコンポーネント
mdx-components.tsx にスタイルとコンポーネントを追加すると、アプリケーション内のすべての MDX ファイルに影響します。
import type { MDXComponents } from 'mdx/types'
import Image, { ImageProps } from 'next/image'
// This file allows you to provide custom React components
// to be used in MDX files. You can import and use any
// React component you want, including inline styles,
// components from other libraries, and more.
const components = {
// Allows customizing built-in components, e.g. to add styling.
h1: ({ children }) => (
<h1 style={{ color: 'red', fontSize: '48px' }}>{children}</h1>
),
img: (props) => (
<Image
sizes="100vw"
style={{ width: '100%', height: 'auto' }}
{...(props as ImageProps)}
/>
),
} satisfies MDXComponents
export function useMDXComponents(): MDXComponents {
return 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} />
}共有レイアウト
MDX ページを囲むレイアウトを共有するには、レイアウトコンポーネントを作成します。
export default function MdxLayout({ children }: { children: React.ReactNode }) {
// Create any shared layout or styles here
return <div style={{ color: 'blue' }}>{children}</div>
}次に、MDX ページにレイアウトコンポーネントをインポートし、MDX コンテンツをレイアウトでラップしてエクスポートします。
import MdxLayout from '../components/mdx-layout'
# Welcome to my MDX page!
export default function MDXPage({ children }) {
return <MdxLayout>{children}</MdxLayout>
}Tailwind typography プラグインの使用
アプリケーションのスタイル設定に Tailwind を使用している場合、@tailwindcss/typography プラグイン を使用すると、Tailwind の設定とスタイルを Markdown ファイルで再利用できます。
このプラグインは、Markdown などのソースから取得したコンテンツブロックに、タイポグラフィスタイルを追加するための prose クラスのセットを提供します。
Tailwind typography のインストール と 共有レイアウト と組み合わせて、必要な prose を追加できます。
MDX ページを囲むレイアウトを共有するには、レイアウトコンポーネントを作成します。
export default function MdxLayout({ children }: { children: React.ReactNode }) {
// Create any shared layout or styles here
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'
# Welcome to my MDX page!
export default function MDXPage({ children }) {
return <MdxLayout>{children}</MdxLayout>
}フロントマター
フロントマターは、YAML のようなキー/値のペアで、ページのデータを格納するために使用できます。@next/mdx はデフォルトではフロントマターをサポートしていませんが、MDX コンテンツにフロントマターを追加するための多くのソリューションがあります。例えば:
@next/mdx は、エクスポートを他の JavaScript コンポーネントと同様に使用することを許可します。
メタデータは、MDX ファイルの外部から参照できるようになりました。
import BlogPost, { metadata } from '@/content/blog-post.mdx'
export default function Page() {
console.log('metadata: ', metadata)
//=> { author: 'John Doe' }
return <BlogPost />
}これは、MDX のコレクションを反復処理してデータ抽出したい場合に一般的なユースケースです。例えば、すべてのブログ投稿からブログインデックスページを作成する場合です。Node's や globby のようなパッケージを使用して、投稿ディレクトリを読み取り、メタデータを抽出できます。fs module
知っておくと良いこと:
fs,globbyなどはサーバーサイドでのみ使用できます。- 完全な動作例については、Portfolio Starter Kit テンプレートを参照してください。
remark および rehype プラグイン
オプションで、remark および rehype プラグインを提供して MDX コンテンツを変換できます。
例えば、GitHub Flavored Markdown をサポートするために remark-gfm を使用できます。
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 = {
// Allow .mdx extensions for files
pageExtensions: ['js', 'jsx', 'md', 'mdx', 'ts', 'tsx'],
// Optionally, add any other Next.js config below
}
const withMDX = createMDX({
// Add markdown plugins here, as desired
options: {
remarkPlugins: [remarkGfm],
rehypePlugins: [],
},
})
// Combine MDX and Next.js config
export default withMDX(nextConfig)Turbopack でのプラグインの使用
@next/mdx の最新バージョンにアップグレードし、文字列を使用してプラグイン名を指定すると、Turbopack でプラグインを使用できます。
import createMDX from '@next/mdx'
/** @type {import('next').NextConfig} */
const nextConfig = {
pageExtensions: ['js', 'jsx', 'md', 'mdx', 'ts', 'tsx'],
}
const withMDX = createMDX({
options: {
remarkPlugins: [
// Without options
'remark-gfm',
// With options
['remark-toc', { heading: 'The Table' }],
],
rehypePlugins: [
// Without options
'rehype-slug',
// With options
['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) {
// either render error UI or throw `mdxSource.error`
}
return <MDXClient {...mdxSource} />
}
export async function getStaticProps() {
// MDX text - can be from a database, CMS, fetch, anywhere...
const res = await fetch('https:...')
const mdxText = await res.text()
const mdxSource = await serialize({ source: mdxText })
return { props: { mdxSource } }
}/mdx-page-remote ルートに移動すると、レンダリングされた MDX が表示されるはずです。
深掘り: Markdown はどのように HTML に変換されますか?
React は Markdown をネイティブに理解しません。Markdown のプレーンテキストは、まず HTML に変換される必要があります。これは remark と rehype を使用して達成できます。
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) // Convert into markdown AST
.use(remarkRehype) // Transform to HTML AST
.use(rehypeSanitize) // Sanitize HTML input
.use(rehypeStringify) // Convert AST into serialized 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 コンパイラをサポートしています。このコンパイラはまだ実験的であり、本番環境での使用は推奨されていません。新しいコンパイラを使用するには、withMDX に渡す際に next.config.js を設定する必要があります。
module.exports = withMDX({
experimental: {
mdxRs: true,
},
})mdxRs は、MDX ファイルの変換方法を設定するためのオブジェクトも受け付けます。
module.exports = withMDX({
experimental: {
mdxRs: {
jsxRuntime?: string // Custom jsx runtime
jsxImportSource?: string // Custom jsx import source,
mdxType?: 'gfm' | 'commonmark' // Configure what kind of mdx syntax will be used to parse & transform
},
},
})役立つリンク
役に立ちましたか?