フューチャー技術ブログ

Next.js公式のMDXプラグインで、Markdownでページを作る機能を試してみた

お客様と一緒にNext.jsのドキュメントの輪読会をやっているのですが、その中で初めて知った機能がMDXというマークダウンでページを作成する機能です。

https://nextjs.org/docs/advanced-features/using-mdx

利用規約とか、プライバシーポリシーのページ、ヘルプページの作成とか、Reactコンポーネントも表示できるので、Storybookがわりのコンポーネントカタログを作るのに便利そうという話になったので、ちょっと試してみました。

設定方法

上記のページに全部書いてあるのでそちらを見ると良いのですが、軽く手順だけ書いておきます。説明をするため、というよりも「これだけの手間ならやってもいいかな」と興味をもってもらうための説明なので実際に作業をする場合は公式ドキュメントの最新情報を確認してください。

まずは必要なパッケージを追加し、

npm install @next/mdx @mdx-js/loader

設定ファイルを少し書き換えます。Next.jsでゼロから作ったnext.config.jsに書き加えるとこんな感じになるかと。

next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
swcMinify: true,
// ↓この行を追加
pageExtensions: ['ts', 'tsx', 'js', 'jsx', 'md', 'mdx'],
}

// ↓これ以下を追加
const withMDX = require('@next/mdx')({
extension: /\.mdx?$/,
options: {
remarkPlugins: [],
rehypePlugins: [],
// `MDXProvider`を使う場合はコメントを外すこと
// providerImportSource: "@mdx-js/react",
},
})
module.exports = withMDX(nextConfig)

Next.js公式機能ではありますが、別パッケージになっています。

markdownのライブラリのプラグインや、HTML処理を行うrehypeのプラグインも設定でき、いろいろな書き方を追加したりできます。GFMサポートを追加するプラグインとかもありますね。

https://github.com/remarkjs/remark/blob/main/doc/plugins.md

さっそく試す。

Markdownの形式のライセンスをコピー&ペーストしてきました。

https://github.com/IQAndreas/markdown-licenses/blob/master/bsd-3.md

pages以下にlicense.mdxという名前で配置してアクセスすると表示されていることがわかります。簡単ですね。

.
└── pages
   ├── _app.tsx
   ├── api
   │   └── hello.ts
   ├── index.tsx
   └── license.mdx # これ!
スクリーンショット_2022-07-29_12.57.25.png

mdxの書き方は以下のサイトで確認できます。

https://mdxjs.com/

JSX同様、{}でJSコードを挟み込めます。getStaticProps()とかのNode.jsのデータフェッチの処理もいつも通りに書けますね。結果はpropsに入れられるので、それをもとに表示することも可能です。

Modified BSD License
====================

_Copyright © {props.year}, {props.holder}
_All rights reserved._

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

(省略)

export async function getStaticProps(context) {
return {
props: {
year: (new Date()).getFullYear(),
holder: "Yoshiki Shibukawa"
}
}
}

権利者情報とか、年情報をサーバーからとってきて埋め込みもできました。getStaticProps()なので静的サイト生成にも使えますね。

スクリーンショット_2022-07-29_15.13.23.png

タグのカスタマイズ機能を試してみる

ドキュメントにはタグのカスタマイズ機能も紹介されていました。これにより、たとえばアプリケーションのメインの機能と、ドキュメントのスタイルの合わせも簡単になります。

特定のmdxページだけのラップでも良いのですが、今回は手間を考えて_app.tsxにまとめて入れてしまいました。mdxの標準構成だと、<h1>とか <p>とかのプレーンなタグを生成するのですが、MDXProviderを使うと、それを自作のタグに置き換えられます。

Tailwind CSSを使ったコンポーネントを作って登録してみたのがこのコードです。

pages/_app.tsx
import '../styles/globals.css'

import { ReactNode } from 'react'
import type { AppProps } from 'next/app'
import { MDXProvider } from '@mdx-js/react'

// 生成されたHTMLで使用するコンポーネント
function H1(props: {children?: ReactNode}) {
return <h1 className="text-blue-600 text-2xl font-bold">{props.children}</h1>
}

function P(props: {children?: ReactNode}) {
return <p className="p-3">{props.children}</p>
}

function Li(props: {children?: ReactNode}) {
return <li className="px-6 py-1 list-item list-outside drop-shadow-lg">🏄 {props.children}</li>
}

const components = {
h1: H1,
p: P,
li: Li,
}

function MyApp({ Component, pageProps }: AppProps) {
return (
<MDXProvider components={components}>
<Component {...pageProps} />
</MDXProvider>
)
}

export default MyApp

スタイルが書きかわりました。簡単ですね。React Bootstrapを使っていたらBootstrapのコンポーネントを、MUIを使っていたらMUIを使うように設定することで、デザインの親和性も上がると思います。

スクリーンショット_2022-07-29_15.13.12.png

まとめ

Next.jsのページにはReactコンポーネントを表示する方法や、ページ全体をラップする書き方とかの紹介はありましたが、getStaticProps()のようなデータ取得関数とpropsの利用や、TypeScriptの型チェックを効かせたカスタマイズ機能の実装の例が公式ドキュメントにはなかったので、ちょっと試行錯誤してみました。

Next.jsのさまざまな機能とも合わせ込みしやすくなっていますし、応用しやすそうに感じました。

また、MDXそのものも今回初めて知ったのですが、Next.js固有のものではなく、独立したパッケージで、Vue.jsとかとも組み合わせられそうですね。